合成事件分为三个阶段:
- 事件注册
- 事件绑定
- 事件执行
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>React Event</title>
<style>
html, body {
margin: 0;
padding: 0
}
</style>
</head>
<body>
<div id="root"></div>
</body>
<script>
const root = document.getElementById('root')
const elementTree = {
type: 'div',
props: {
onClick: () => {
console.log('父元素冒泡')
},
onClickCapture: (e) => {
// e.stopPropagation()
console.log('父元素捕获')
}
},
children: {
type: 'button',
props: {
onClick: () => {
console.log('子元素冒泡')
},
onClickCapture: (e) => {
// e.stopPropagation()
console.log('子元素捕获')
}
},
children: '点击'
}
}
const render = (element, parentNode) => {
const type = element.type
let dom = null
if(typeof element === 'string' || typeof element === 'number'){
dom = document.createTextNode(element)
} else {
dom = document.createElement(type)
}
const returnFiber = parentNode.__reactFiber || null
const fiber = {
type,
stateNode: dom,
return: returnFiber
}
dom.__reactFiber = fiber
dom.__reactProps = element.props
if(element.children){
render(element.children, dom)
}
parentNode.appendChild(dom)
}
render(elementTree, root)
</script>
</html>
这一阶段包括:
- 收集所有的原生事件名称
allNativeEvents
,并生成原生事件名称和合成事件名称的映射关系topLevelEventsToReactNames
。 - 同时,生成所有的合成事件对象构造函数
// 第一阶段 注册事件 以及 模拟合成事件
// 创建合成事件的工厂函数
function createSyntheticEvent(Interface){
function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget){
this._reactName = reactName
this._targetInst = targetInst
this.type = reactEventType
this.nativeEvent = nativeEvent
this.target = nativeEventTarget
this.currentTarget = null // 当前的事件源
for(const propName in Interface){
this[propName] = nativeEvent[propName]
}
this.isDefaultPrevented = () => false
this.isPropagationStopped = () => false
return this
}
Object.assign(SyntheticBaseEvent.prototype, {
preventDefault(){
// event.defaultPrevented返回一个布尔值,表明当前事件是否调用了event.preventDefault()方法。
// 因此在模拟的preventDefault方法里需要手动设置这个属性
this.defaultPrevented = true
const event = this.nativeEvent
if(event.preventDefault){
event.preventDefault()
} else {
event.returnValue = true; // IE
}
this.isDefaultPrevented = () => true
},
stopPropagation(){
const event = this.nativeEvent
if(event.stopPropagation){
event.stopPropagation()
} else {
event.cancelBubble = true; // IE
}
this.isPropagationStopped = () => true
}
})
return SyntheticBaseEvent
}
const MouseEventInterface = {
clientX: 0,
clientY: 0
}
const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface)
// 将浏览器支持的所有原生事件枚举出来,下面的事件两两一对,奇数位代表原生事件名称,
// 偶数位用于模拟react事件名称,比如 'keyDown' 用于 'on' + 'KeyDown',或者 'on' + 'KeyDown' + 'Capture'
const discreteEventPairsForSimpleEventPlugin = [
'click', 'Click',
// 'keydown', 'KeyDown',
// 'keypress', 'KeyPress',
// 'keyup', 'KeyUp',
// 'mousedown', 'MouseDown',
// 'mouseup', 'MouseUp'
]
const topLevelEventsToReactNames = new Map()
const allNativeEvents = new Set()
// const registrationNameDependencies = {}
function registerSimpleEvents(){
for(let i = 0; i < discreteEventPairsForSimpleEventPlugin.length; i+=2){
const topEvent = discreteEventPairsForSimpleEventPlugin[i] // 原生事件名称
const reactName = 'on' + discreteEventPairsForSimpleEventPlugin[i+1] // react事件
topLevelEventsToReactNames.set(topEvent, reactName)
// registrationNameDependencies[reactName] = [topEvent]
// registrationNameDependencies[reactName + 'Capture'] = [topEvent]
allNativeEvents.add(topEvent)
}
}
registerSimpleEvents()
这一阶段包括:
- 遍历
allNativeEvents
中的所有原生事件名称,给容器root
注册所有的捕获和冒泡事件
// 第二阶段 事件绑定
// 根据收集的allNativeEvents给容器root注册所有的事件
function listenToAllSupportedEvents(container){
allNativeEvents.forEach(domEventName => {
listenToNativeEvent(domEventName, false, container) // 绑定冒泡事件
listenToNativeEvent(domEventName, true, container) // 绑定捕获事件
})
}
function listenToNativeEvent(domEventName, isCaptruePhaseListener, rootContainerElement){
let listener = dispatchEvent.bind(null, domEventName, isCaptruePhaseListener, rootContainerElement)
if(isCaptruePhaseListener){
rootContainerElement.addEventListener(domEventName, listener, true)
} else {
rootContainerElement.addEventListener(domEventName, listener, false)
}
}
listenToAllSupportedEvents(root)
这一阶段包括:
- 点击按钮,触发事件执行,首先执行
dispatchEvent
方法 - 执行
extractEvents
方法收集所有的捕获阶段或者冒泡阶段事件,并生成对应的原生事件的合成事件对象,填充dispatchQueue
数组 processDispatchQueue
按顺序执行对应的事件处理函数
// 第三阶段 事件触发
// 事件触发首先执行的是dispatchEvent
function dispatchEvent(domEventName, isCaptruePhaseListener, targetContainer, nativeEvent){
// 获取原生的事件源
const target = nativeEvent.target || nativeEvent.srcElement || window
// 获取fiber实例
const targetInst = target.__reactFiber;
dispatchEventForPluginEventSystem(
domEventName,
isCaptruePhaseListener,
nativeEvent,
targetInst,
targetContainer
)
}
function dispatchEventForPluginEventSystem(domEventName, isCaptruePhaseListener, nativeEvent, targetInst, targetContainer){
const nativeEventTarget = nativeEvent.target;
const dispatchQueue = []
// 提取事件处理函数,填充 dispatchQueue 数组
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
isCaptruePhaseListener,
targetContainer
)
processDispatchQueue(dispatchQueue, isCaptruePhaseListener)
}
function processDispatchQueue(dispatchQueue, isCaptruePhaseListener){
for(let i = 0; i < dispatchQueue.length; i++){
const { event, listeners } = dispatchQueue[i]
if(isCaptruePhaseListener){
for(let i = listeners.length - 1; i >= 0; i--){
const [ currentTarget, listener ] = listeners[i]
if(event.isPropagationStopped()){
return
}
execDispatch(event, listener, currentTarget)
}
} else {
for(let i = 0; i < listeners.length; i++){
const [ currentTarget, listener ] = listeners[i]
if(event.isPropagationStopped()){
return
}
execDispatch(event, listener, currentTarget)
}
}
}
}
function execDispatch(event, listener, currentTarget){
event.currentTarget = currentTarget
listener(event)
event.currentTarget = null
}
// 提取事件处理函数 生成合成事件对象 填充dispatchQueue数组
function extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, isCaptruePhaseListener, targetContainer){
const reactName = topLevelEventsToReactNames.get(domEventName)
let SyntheticEventCtor;
let reactEventType = domEventName
// 不同的事件,合成事件对象是不一样的,对应不同合成事件构造函数
switch(domEventName){
case 'click':
SyntheticEventCtor = SyntheticMouseEvent;
break
default:
break;
}
const listeners = accumulateSinglePhaseListeners(
targetInst,
reactName,
nativeEvent.type,
isCaptruePhaseListener
)
if(listeners.length){
const event = new SyntheticEventCtor(
reactName,
reactEventType,
targetInst,
nativeEvent,
nativeEventTarget
)
dispatchQueue.push({
event,
listeners
})
}
}
function accumulateSinglePhaseListeners(targetFiber, reactName, nativeType, inCapturePhase){
const captureName = reactName + 'Capture'
const reactEventName = inCapturePhase ? captureName : reactName
const listeners = []
let instance = targetFiber
while(instance){
const stateNode = instance.stateNode
const listener = stateNode.__reactProps[reactEventName]
if(listener){
listeners.push([instance, listener, stateNode])
}
instance = instance.return
}
return listeners
}
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>React Event</title>
<style>
html, body {
margin: 0;
padding: 0
}
</style>
</head>
<body>
<div id="root"></div>
</body>
<script>
const root = document.getElementById('root')
const elementTree = {
type: 'div',
props: {
onClick: () => {
console.log('父元素冒泡')
},
onClickCapture: (e) => {
// e.stopPropagation()
console.log('父元素捕获')
}
},
children: {
type: 'button',
props: {
onClick: () => {
console.log('子元素冒泡')
},
onClickCapture: (e) => {
// e.stopPropagation()
console.log('子元素捕获')
}
},
children: '点击'
}
}
const render = (element, parentNode) => {
const type = element.type
let dom = null
if(typeof element === 'string' || typeof element === 'number'){
dom = document.createTextNode(element)
} else {
dom = document.createElement(type)
}
const returnFiber = parentNode.__reactFiber || null
const fiber = {
type,
stateNode: dom,
return: returnFiber
}
dom.__reactFiber = fiber
dom.__reactProps = element.props
if(element.children){
render(element.children, dom)
}
parentNode.appendChild(dom)
}
render(elementTree, root)
// 第一阶段 注册事件 以及 模拟合成事件
// 创建合成事件的工厂函数
function createSyntheticEvent(Interface){
function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget){
this._reactName = reactName
this._targetInst = targetInst
this.type = reactEventType
this.nativeEvent = nativeEvent
this.target = nativeEventTarget
this.currentTarget = null // 当前的事件源
for(const propName in Interface){
this[propName] = nativeEvent[propName]
}
this.isDefaultPrevented = () => false
this.isPropagationStopped = () => false
return this
}
Object.assign(SyntheticBaseEvent.prototype, {
preventDefault(){
// event.defaultPrevented返回一个布尔值,表明当前事件是否调用了event.preventDefault()方法。
// 因此在模拟的preventDefault方法里需要手动设置这个属性
this.defaultPrevented = true
const event = this.nativeEvent
if(event.preventDefault){
event.preventDefault()
} else {
event.returnValue = true; // IE
}
this.isDefaultPrevented = () => true
},
stopPropagation(){
const event = this.nativeEvent
if(event.stopPropagation){
event.stopPropagation()
} else {
event.cancelBubble = true; // IE
}
this.isPropagationStopped = () => true
}
})
return SyntheticBaseEvent
}
const MouseEventInterface = {
clientX: 0,
clientY: 0
}
const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface)
// 将浏览器支持的所有原生事件枚举出来,下面的事件两两一对,奇数位代表原生事件名称,
// 偶数位用于模拟react事件名称,比如 'keyDown' 用于 'on' + 'KeyDown',或者 'on' + 'KeyDown' + 'Capture'
const discreteEventPairsForSimpleEventPlugin = [
'click', 'Click',
// 'keydown', 'KeyDown',
// 'keypress', 'KeyPress',
// 'keyup', 'KeyUp',
// 'mousedown', 'MouseDown',
// 'mouseup', 'MouseUp'
]
const topLevelEventsToReactNames = new Map()
const allNativeEvents = new Set()
// const registrationNameDependencies = {}
function registerSimpleEvents(){
for(let i = 0; i < discreteEventPairsForSimpleEventPlugin.length; i+=2){
const topEvent = discreteEventPairsForSimpleEventPlugin[i] // 原生事件名称
const reactName = 'on' + discreteEventPairsForSimpleEventPlugin[i+1] // react事件
topLevelEventsToReactNames.set(topEvent, reactName)
// registrationNameDependencies[reactName] = [topEvent]
// registrationNameDependencies[reactName + 'Capture'] = [topEvent]
allNativeEvents.add(topEvent)
}
}
registerSimpleEvents()
// 第二阶段 事件绑定
// 根据收集的allNativeEvents给容器root注册所有的事件
function listenToAllSupportedEvents(container){
allNativeEvents.forEach(domEventName => {
listenToNativeEvent(domEventName, false, container) // 绑定冒泡事件
listenToNativeEvent(domEventName, true, container) // 绑定捕获事件
})
}
function listenToNativeEvent(domEventName, isCaptruePhaseListener, rootContainerElement){
let listener = dispatchEvent.bind(null, domEventName, isCaptruePhaseListener, rootContainerElement)
if(isCaptruePhaseListener){
rootContainerElement.addEventListener(domEventName, listener, true)
} else {
rootContainerElement.addEventListener(domEventName, listener, false)
}
}
listenToAllSupportedEvents(root)
// 第三阶段 事件触发
// 事件触发首先执行的是dispatchEvent
function dispatchEvent(domEventName, isCaptruePhaseListener, targetContainer, nativeEvent){
// 获取原生的事件源
const target = nativeEvent.target || nativeEvent.srcElement || window
// 获取fiber实例
const targetInst = target.__reactFiber;
dispatchEventForPluginEventSystem(
domEventName,
isCaptruePhaseListener,
nativeEvent,
targetInst,
targetContainer
)
}
function dispatchEventForPluginEventSystem(domEventName, isCaptruePhaseListener, nativeEvent, targetInst, targetContainer){
const nativeEventTarget = nativeEvent.target;
const dispatchQueue = []
// 提取事件处理函数,填充 dispatchQueue 数组
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
isCaptruePhaseListener,
targetContainer
)
processDispatchQueue(dispatchQueue, isCaptruePhaseListener)
}
function processDispatchQueue(dispatchQueue, isCaptruePhaseListener){
for(let i = 0; i < dispatchQueue.length; i++){
const { event, listeners } = dispatchQueue[i]
if(isCaptruePhaseListener){
for(let i = listeners.length - 1; i >= 0; i--){
const [ currentTarget, listener ] = listeners[i]
if(event.isPropagationStopped()){
return
}
execDispatch(event, listener, currentTarget)
}
} else {
for(let i = 0; i < listeners.length; i++){
const [ currentTarget, listener ] = listeners[i]
if(event.isPropagationStopped()){
return
}
execDispatch(event, listener, currentTarget)
}
}
}
}
function execDispatch(event, listener, currentTarget){
event.currentTarget = currentTarget
listener(event)
event.currentTarget = null
}
// 提取事件处理函数 生成合成事件对象 填充dispatchQueue数组
function extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, isCaptruePhaseListener, targetContainer){
const reactName = topLevelEventsToReactNames.get(domEventName)
let SyntheticEventCtor;
let reactEventType = domEventName
// 不同的事件,合成事件对象是不一样的,对应不同合成事件构造函数
switch(domEventName){
case 'click':
SyntheticEventCtor = SyntheticMouseEvent;
break
default:
break;
}
const listeners = accumulateSinglePhaseListeners(
targetInst,
reactName,
nativeEvent.type,
isCaptruePhaseListener
)
if(listeners.length){
const event = new SyntheticEventCtor(
reactName,
reactEventType,
targetInst,
nativeEvent,
nativeEventTarget
)
dispatchQueue.push({
event,
listeners
})
}
}
function accumulateSinglePhaseListeners(targetFiber, reactName, nativeType, inCapturePhase){
const captureName = reactName + 'Capture'
const reactEventName = inCapturePhase ? captureName : reactName
const listeners = []
let instance = targetFiber
while(instance){
const stateNode = instance.stateNode
const listener = stateNode.__reactProps[reactEventName]
if(listener){
listeners.push([instance, listener, stateNode])
}
instance = instance.return
}
return listeners
}
</script>
</html>