Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 17 题:React 事件绑定原理 #23

Open
lgwebdream opened this issue Jun 19, 2020 · 6 comments
Open

第 17 题:React 事件绑定原理 #23

lgwebdream opened this issue Jun 19, 2020 · 6 comments
Labels
React teach_tag 沪江 company 滴滴 company

Comments

@lgwebdream
Copy link
Owner

lgwebdream commented Jun 19, 2020

欢迎在下方发表您的优质见解

@lgwebdream lgwebdream added React teach_tag 滴滴 company 沪江 company labels Jun 19, 2020
@Genzhen
Copy link
Collaborator

Genzhen commented Jun 23, 2020

React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。
react事件绑定原理

@Genzhen
Copy link
Collaborator

Genzhen commented Jun 23, 2020

1)事件注册

事件注册流程

  • 组件装载 / 更新。
  • 通过lastProps、nextProps判断是否新增、删除事件分别调用事件注册、卸载方法。
  • 调用EventPluginHub的enqueuePutListener进行事件存储
  • 获取document对象。
  • 根据事件名称(如onClick、onCaptureClick)判断是进行冒泡还是捕获。
  • 判断是否存在addEventListener方法,否则使用attachEvent(兼容IE)。
  • 给document注册原生事件回调为dispatchEvent(统一的事件分发机制)。

2)事件存储

事件存储

  • EventPluginHub负责管理React合成事件的callback,它将callback存储在listenerBank中,另外还存储了负责合成事件的Plugin。
  • EventPluginHub的putListener方法是向存储容器中增加一个listener。
  • 获取绑定事件的元素的唯一标识key。
  • 将callback根据事件类型,元素的唯一标识key存储在listenerBank中。
  • listenerBank的结构是:listenerBank[registrationName][key]。
{
    onClick:{
        nodeid1:()=>{...}
        nodeid2:()=>{...}
    },
    onChange:{
        nodeid3:()=>{...}
        nodeid4:()=>{...}
    }
}

3)事件触发执行

事件触发执行

  • 触发document注册原生事件的回调dispatchEvent
  • 获取到触发这个事件最深一级的元素
    这里的事件执行利用了React的批处理机制

代码示例

<div onClick={this.parentClick} ref={ref => this.parent = ref}>
      <div onClick={this.childClick} ref={ref => this.child = ref}>
          test
     </div>
</div>
  • 首先会获取到this.child
  • 遍历这个元素的所有父元素,依次对每一级元素进行处理。
  • 构造合成事件。
  • 将每一级的合成事件存储在eventQueue事件队列中。
  • 遍历eventQueue。
  • 通过isPropagationStopped判断当前事件是否执行了阻止冒泡方法。
  • 如果阻止了冒泡,停止遍历,否则通过executeDispatch执行合成事件。
  • 释放处理完成的事件。

4)合成事件

合成事件

  • 调用EventPluginHub的extractEvents方法。
  • 循环所有类型的EventPlugin(用来处理不同事件的工具方法)。
  • 在每个EventPlugin中根据不同的事件类型,返回不同的事件池。
  • 在事件池中取出合成事件,如果事件池是空的,那么创建一个新的。
  • 根据元素nodeid(唯一标识key)和事件类型从listenerBink中取出回调函数
  • 返回带有合成事件参数的回调函数

5)总流程

总流程

@fengmiaosen
Copy link

很详细

@Genzhen Genzhen closed this as completed Jul 20, 2020
@Genzhen Genzhen reopened this Jul 29, 2020
@zjg910723
Copy link

如果遇到了事件冒泡的节点,是不是render的时候就不会更新事件冒泡以上的父组件,还是说更新组件更props的变更有关

@CaiYifei
Copy link

这一块的判断是不是写错了?跟“如果阻止了冒泡,停止遍历,否则通过executeDispatch执行合成事件。”这个相反

Capture

@lazybonee
Copy link

另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。

这里是不是有问题的? https://codesandbox.io/s/withered-snow-3dq39?file=/src/App.js 在这个例子中调用了event.preventDefault,但并未阻止冒泡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
React teach_tag 沪江 company 滴滴 company
Projects
None yet
Development

No branches or pull requests

6 participants