React17事件机制

为什么要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()不能阻止原生事件向根传播,但是可以阻止根容器向页面顶层传播。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值