带着问题阅读探索
一共分为三部分:
react对于不同的事件,比如onClick和onChange,会有不同的事件插件处理。
const registrationNameModules = {onBlur: SimpleEventPlugin,onClick: SimpleEventPlugin,onClickCapture: SimpleEventPlugin,onChange: ChangeEventPlugin,onChangeCapture: ChangeEventPlugin,onMouseEnter: EnterLeaveEventPlugin,onMouseLeave: EnterLeaveEventPlugin,...
}
registrationNameModules存放着每个事件与之对应的处理插件的映射。比如onClick,就是SimpleEventPlugin。对于onChange,会使用ChangeEventPlugin处理…
看看SimpleEventPlugin和ChangeEventPlugin的真面目。
SimpleEventPlugin是一个对象,registerSimpleEvents顾名思义就是用来注册simple的事件的。
可以看到,遍历simpleEventPluginEvents数组,如keyUp,mouseDown都属于simple的事件。然后对每个事件,调用
register('keyUp', 'onKeyUp')
第一个参数就是原生事件,第二个参数就是react的事件。
然后调用registerTwoPhaseEvent( 'onKeyUp', ['keyUp'])
,
这就是simpleEventPlugin的registerEvents做的事情。
而onChange对应的ChangeEventPlugin
注册onChange,而onChange依赖的事件是change,click,…等诸多个原生事件。
而每个插件的extractEvents顾名思义就是提取事件源对象event。对于不同的事件,事件源对象不同。
对于不同的合成事件,有不同的处理逻辑;对应的事件源对象也不同,react事件和事件源是自己合成的。
而还有一个对象registrationNameDependencies,
就是上面说到每个插件调用registerEvent的时候,最终调用的是registerDirectEvent,而他会将事件的依赖收集到registrationNameDependecies上面,比如上面的onKeyup: [keyUp],而onChange就是对应: ['blur,‘change’…]
最终这个对象就长得像
{onBlur: ['blur'],onClick: ['click'],onClickCapture: ['click'],onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'],onMouseEnter: ['mouseout', 'mouseover'],onMouseLeave: ['mouseout', 'mouseover'],...
}
这个对象保存了React事件和原生事件的对应关系,发现有react事件的时候,就会通过这个对象找到原生事件数组,然后逐一绑定。
react在处理props的啥时候,如果遇到了事件比如onCLick,就会通过addEventListener注册原生事件。
然后我们绑定的事件比如
const Test = () => {
return 123
}
handleClick事件,他会存在div fiber上面的memoizedProps上面如
testFiber.child = divFiber
divFiber.child = testFiber
divFiber.memoizedProps = {onClick: handleClick}
接着需要通过react事件注册原生的事件。
react在处理props的时候,如果发现是合成事件的话,比如onClick,就会调用legacyListenToEvent 函数,
function diffProperties(){/* 判断当前的 propKey 是不是 React合成事件 */if(registrationNameModules.hasOwnProperty(propKey)){/* 这里多个函数简化了,如果是合成事件, 传入成事件名称 onClick ,向document注册事件 */legacyListenToEvent(registrationName, document);}
}
上面介绍了registrationNameDependencies是存取react事件跟原生事件的绑定。如onChange对应[‘blur’, ‘change’, ‘click’, ‘focus’, ‘input’, ‘keydown’, ‘keyup’, ‘selectionchange’],通过react事件获取原生事件,通过for循环进行绑定。
其次,真正绑定到container上面的函数,并不是我们直接写的handleClcik函数,而是React统一的事件处理还能输,dispatchevent,只要是react事件触发,首先执行的就是dispatchEvent
给容器注册事件的时候,通过.bind传入了一些参数,这样dispatchEvent才能知道是什么事件触发。
老的事件版本,使用的批量更新依然是通过开关开启或者关闭的。
比如点击一次之后,
dispatchEvent触发,传入真实的事件源button元素本身。
然后通过button dom可以找到对应的fiber
因为dom跟fiber之间的关系是
fiber.stateNode = dom
dom.xxxKey = fiber
伪代码类似于
export function batchedEventUpdates(fn,a){isBatchingEventUpdates = true; //打开批量更新开关try{fn(a) // 事件在这里执行}finally{isBatchingEventUpdates = false //关闭批量更新开关}
}
我们知道react事件的事件源也是react自己合成的,并不是原生的事件源。
onClick是由SimpleEventPlugin处理的,
对于onCLick,他的事件源就是SyntheticMouseEvent。所以第二阶段的模型就是:
(图片来自掘金大佬的课程 https://juejin.cn/book/6945998773818490884/section/6959723748450631694)
在第一步通过dom获取的fiber,从这个ifber往上遍历,遇到是元素类型的,比如div,p这些,就会通过一个数组来收集事件。
export default function Test(){const handleClick1 = () => console.log(1)const handleClick2 = () => console.log(2)const handleClick3 = () => console.log(3)const handleClick4 = () => console.log(4)return handleClick3 } onClickCapture={ handleClick4 } >
}
点击button
数组的顺序应该是: [ handleClick4, handleClick2, handleClick1, handleClick3 ]。
先看看事件如何执行的,react提供了event.isPropagationStopped来判断是否已经阻止事件冒泡。
数组通过for循环依次执行,而当调用event.stopPropagation之后,后面一个事件的执行event.isPropagationStopped就是true了,就会退出for循环,后面的函数不再执行,以此模拟阻止事件冒泡。
17版本之前的事件,虽说模拟了事件冒泡和俘获,但是本质上,执行的时机,都是在冒泡阶段。而新版本的事件处理,修正了这个问题。让我们看看新版本的事件系统。
未完待续