React17事件机制

  • Post author:
  • Post category:其他




为什么要React实现自己的事件机制

● 将事件代理到根节点,减少事件监听器创建 节省内存

● 磨平浏览器的差异(阻止事件传播只需:event.stopPropagation)

● 只要在对应节点编写onClick、onClickCapture 可完成节点冒泡、捕获阶段监听,统一写法



事件分类


react对事件进行分类,不同事件通过不同类型的事件插件处理


● 简单事件 SimpleEventPlugin( onClick、onKeyDown 、…)

● 输入前事件 BeforeInputEventPlugin ( onBeforeInput 、 … )

● 表单修改事件 ChangeEventPlugin ( onChange 、…)

● 鼠标进出事件 EnterLeaveEventPlugin ( onMouseEnter… )

● 选择事件 SelectEventPlugin (onSelect)

分类不代表依赖的原生事件没有交集, 简单事件的onKeyDown、输入前事件onCompositionStart依赖、表单修改事件onChange 都依赖原生事件keydown。react对一些事件也进行了包装如: onChange事件,兼容不同表单的修改事件 依赖了原生事件change, click, focusin, input,keydown,keyup,selectionchange



事件收集

react对事件做代理,需要知道浏览器知道的事件,这些事件都硬编码在各个事件插件中。对所需代理的原生事件,会以原生事件名字符串形式存储在allNativeEvents中并在registrationNameDependencies中储存react事件名到依赖的原生事件名数的映射。

事件的收集是通过各个事件插件各自收集注册。页面加载会执行各个插件的registerEvents,将所有依赖的原生事件都注册到allNativeEvents中,并在registrationNameDependencies中存储映射关系

对原生事件不支持冒泡的事件,硬编码的形式存储在nonDelegatedEvents集合中; 不支持冒泡阶段事件和支持冒泡阶段事件处理方式不一样

● 不支持冒泡事件 -> 非代理事件

● 支持冒泡阶段事件 -> 代理事件


// react代码加载时就会执行各个事件的插件
SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();

// 执行完成后allNativeEvents集合就有所需代理的原生事件
allNativeEvents = [click, input, ...];

// nonDelegatedEvents 不支持冒泡阶段原生事件集合
nonDelegatedEvents = [...];

// 保存react事件和其依赖的事件的映射
registrationNameDependencies =  {
onClick: [ 'click' ],
onChange: ['change','click','focusin','focusout','input','keydown','keyup','selectionchange'],
...
}



事件代理
  • 代理事件


    在ReactDOM.render(element, container)时,将事件委托代理到根;在ReactDom.render实现中,创建fiberRoot后, 开始构建fiber树前,调用 listenToAllSupportedEvents进行事件绑定委托。

  • 非代理事件(对不可冒泡的事件都进行了冒泡模拟)


    事件代理在Dom实例创建阶段(render阶段的completeWork阶段),通过调用finalizeInitialChildren为实例dom设置属性时 判断dom节点类型添加响应的冒泡阶段监听器

  • 合成事件


    react对原生事件跨浏览器的包装器,兼容了所有浏览器,拥有和浏览器原生事件相同的接口;使用小驼峰命名(如click 冒泡: onClick, 捕获: onClickCapture)



事件触发


当页面上触发了特定的事件时,如点击事件 click,就会触发绑定在根元素上的事件回调函数,也就是之前绑定了参数的dispatchEvent,而dispatchEvent在内部最终会调用dispatchEventsForPlugins

● 根据原生事件名,得到对应的react事件名

● 判断需要使用的合成事件构造函数

● 根据绑定的事件标记得出事件是否捕获阶段

● 从触发事件的 DOM 实例对应的 fiber 节点开始,向上遍历 fiber 树,判断遍历到的 fiber 是否宿主类型 fiber 节点,是的话判断在其 props 上是否存在 React 事件名同名属性,如果存在,则 push 到数组中,遍历结束即可收集由叶子节点到根节点的回调函数

● 如果收集的回调数值不为空,则实例化对应的合成事件,并与收集的回调函数一同收集到dispatchQueue中

磨平浏览器差异

● 通过事件normalize, 在不同的浏览器中拥有一致属性

● 声明了各种事件接口,磨平浏览器中的差异

16 -> 17

● 由document 改为react根容器

● 存在多个react应用, 都会在顶层document注册处理器,其中一个调用了e.stopPropagation(), 无法阻止事件冒泡到外部树,真实事件已传播

减少多个react应用并存产生的问题;事件系统更贴近现在浏览器表现

事件代理阶段

16: 事件委托在冒泡阶段,当事件冒泡到document后触发绑定的回调,在回调中重新模拟一次 捕获-冒泡行为. e.stopPropagation()无法阻止原生的原生冒泡或捕获,因为已经执行完成。

17: 事件委托分别在捕获和冒泡阶段

  • 根容器接收到捕获事件时,先触发一次react捕获阶段,然后在执行原生的捕获传播,在捕获调用e.stopPropagation()能阻止原生事件传播
  • 接收冒泡事件时,会触发react事件的冒泡阶段,原生事件的冒泡已经传播到了根,在冒泡阶段调用e.stopPropagation()不能阻止原生事件向根传播,但是可以阻止根容器向页面顶层传播。



版权声明:本文为weixin_48065262原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。