前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >​React太劝退,通过anu学合成事件

​React太劝退,通过anu学合成事件

作者头像
公众号@魔术师卡颂
发布2020-10-10 10:37:53
5910
发布2020-10-10 10:37:53
举报
文章被收录于专栏:魔术师卡颂魔术师卡颂

相信React开发者或多或少听说过React合成事件(SyntheticEvent)这一概念。

合成事件这块源码代码量多、耦合了很多其他逻辑,读起来很劝退。

最近刚好在改一个anubug,发现anu合成事件实现的简单易懂。为什么不通过anu来学合成事件呢?

anu是什么

anu是司徒正美老师开发的类React框架,他的特点是:

  • 支持React16的各种新功能
  • 跑通官方近800多个单元测试
  • 支持React全家桶
  • 支持99%的antd组件

以上是面向开发者的特点。

源码层面,anu的架构和Reactv17是很像的,体积却只有React的1/3,通过他来学习React源码的一些流程再合适不过了。

让我们开始吧。

合成事件是什么、有什么用

合成事件是React在浏览器原有捕获->目标->冒泡事件运行机制的基础上重新实现的一套事件运行机制。

为什么要在浏览器事件运行机制之上再重新造轮子呢?最主要的原因是:

浏览器原生实现中,event触发后会在DOM树中依次完成捕获->目标->冒泡

在此过程中经过的DOM如果注册了event handler,则handler会被调用。

而在React内部,并不直接操作DOM,而是操作一棵与DOM树有映射关系的虚拟DOM树(fiber树)。

比如,对于如下应用:

代码语言:javascript
复制
function App() {
  return (
    <div>
      <p onClick={() => console.log('click')}>click~</p>
    </div>
  )
}

ReactDOM.render(<App/>, root);

DOM树与fiber树分别为:

代码语言:javascript
复制
DOM树:     fiber树:

 html     FiberRootNode
  |            |
 body       rootFiber
  |            |
 div          App fiber
  |            |
  p           div fiber
               |
               p fiber

可见,DOM树与fiber树并不是一一对应的。

onClick handler作为props保存在p对应的fiber上,而不是p DOM上。

所以React需要模拟DOM树中事件的传递机制,实现一套类似机制在fiber树中传递事件。

当重新实现整套事件机制后,要在其上再增加一些特性就再容易不过了,比如:

  1. 抹平不同浏览器事件机制的不同(IE说的就是你)
  2. 对事件的定制化需求。

比如在React中,表单组件的change事件的触发时机其实对标的是原生DOM中的input事件。

再比如在React中,focus事件是由原生DOM中的focusinfocusout实现的。

  1. 优先级机制。

React中,不同事件的优先级不同。在不同事件的event handler中触发的setState会以不同优先级执行。

合成事件的实现

以下实现的代码皆来自anu

合成事件的实现原理很好理解:

  1. document绑定event handler,通过事件委托的方式监听事件
  2. 当事件触发后,通过e.target获取触发事件的DOM,找到DOM对应的fiber
  3. 从该fiber根fiber遍历,收集遍历过程中所有绑定了该类型事件的fiberevent handler,保存在数组paths
  4. 遍历paths,依次调用event handler,模拟捕获流程
  5. 遍历paths.reverse(),依次调用event handler,模拟冒泡流程

接下来我们以click事件举例:

  1. 调用addGlobalEvent('click')注册全局handler用于事件委托

其中dispatchEventhandler

代码语言:javascript
复制
function addGlobalEvent(name, capture) {
  if (!globalEvents[name]) {
    globalEvents[name] = true;
    // addEventListener的实现
    addEvent(document, name, dispatchEvent, capture);
  }
}
  1. 当点击DOM,触发dispatchEvent
代码语言:javascript
复制
function dispatchEvent(e, type, endpoint) {
  
  e = new SyntheticEvent(e);
  // ...一些前置处理,省略

  Renderer.batchedUpdates(function() {
    // 3. 通过collectPaths收集fiber沿途的click handler
    let paths = collectPaths(e.target, terminal, {});
    let captured = bubble + 'capture';
    // 4. 模拟捕获流程
    triggerEventFlow(paths, captured, e);

    if (!e._stopPropagation) {
      // 5. 模拟冒泡流程
      triggerEventFlow(paths.reverse(), bubble, e);
    }
  }, e);
}

其中triggerEventFlow就是简单的遍历数组并执行回调。

代码语言:javascript
复制
function triggerEventFlow(paths, prop, e) {
  for (let i = paths.length; i--; ) {
    let path = paths[i];
    let fn = path.events[prop];
    if (isFn(fn)) {
        e.currentTarget = path.node;
        fn.call(void 666, e);
        if (e._stopPropagation) {
            break;
        }
    }
  }
}

总结

现在我们知道了,当向p组件传递onClick props,组件本身并不会绑定对应的handler,组件销毁后也不会有click handler的解绑操作。

“p对应DOM响应点击事件”的原因是:

DOM对应的fiber上的onClick回调在dispatchEvent方法中的collectPaths中被收集,并在triggerEventFlow中被调用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-10-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 魔术师卡颂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • anu是什么
  • 合成事件是什么、有什么用
  • 合成事件的实现
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档