jerytang

9 天前

react是如何实现冒泡的

本文作者:IMWeb jerytang 原文出处:IMWeb社区 未经同意,禁止转载

react 自己实现了一套事件冒泡机制,将所有事件都用代理的方式绑定到 document上。这里谈下我对 react 的冒泡实现的理解,不对的请指出。

两种事件模型

我们知道,在标准里面是支持 bubble 和 capture 两种事件模型的。

React 也支持这两种事件模型,很大可能你还没有使用过 React 的事件捕获,看下面的例子:

使用事件冒泡,如果点击按钮,childOnclick 会被触发,然后 parentOnclick 会被触发,如果 childOnClick 中调用了 event.stopPropagation(),阻止了冒泡,那么 parentOnClick 就不会触发了。这个过程是 child 到 parent,是自底向上的,就像冒泡一样。

<div onClick={this.parentOnClick}>
  <button onClick={this.childOnClick}>冒泡的事件!</button>
</div>

像web标准一样,其实也可以反过来,先是父级组件先触发事件,然后再一级往下传递,这种方式被称为捕获。使用 onEventNameCapture,就是使用捕获的方式,下面的代码会先执行 parentOnClick,再执行 childOnClick,如果在 parentOnClick 调用了 stopPropagation 阻止事件传递,那么就会导致 childOnClick 不会被触发。

<div onClickCapture={this.parentOnClick}>
  <button onClick={this.childOnClick}>捕获的事件!</button>
</div>

为什么

为什么会有这两种事件模型呢?

一方面从历史沿革来看,在浏览器的早期,Netscape 浏览器是使用的 capture 事件模型,而 IE 使用的是冒泡模型,后来的标准里面就有了这两种模型可选:

element.addEventListner(name, fn, useCapture)

useCapture 为 true 表示使用捕获,useCapture 为 false 表示使用冒泡。

现在,大家从使用习惯上来讲,使用冒泡会比较多。addEventListner 的第 3 个参数 useCapture 的默认值也是 false.

另一方面,从性能上来讲,捕获模型的性能会好一丢丢,见 这里的讨论.

react/类react框架是如何实现冒泡机制的?

前面是铺垫,现在引入主题。

有一个问题一直困惑我:有些事件是不支持事件冒泡的,比如 blur 事件,那么 react 是如何实现这类事件冒泡的?

<div id="el">
  <input type="text" id="input">
</div>

如果使用原生的方式,在 el 绑定 blur 事件,在 input 上也绑定 blur 事件,当 input 触发 blur 事件,其父元素并不会触发 blur 事件。下面的代码,只会输出 #2.

const el = document.querySelector('#el');
const ip = document.querySelector('#input');

el.addEventListener('blur', function(e) {
  console.log(`#1 new ${e.target.value}`)
}, false)

ip.addEventListener('blur', function(e) {
  console.log(`#2 ${e.target.value}`)
})

而在 react 中,当 input blur 事件触发后,会按照 #1 #2 的顺序输出

<div onBlur={this.parentOnBlur}>
  <input type="text" onBlur={this.childOnBlur}>
</div>

如果你使用的是一些类 react 的方案,比如 react-lite,可能会存在bug的,上面的代码,在 react-lite 不能按照预期的方式冒泡。

实现方案一

在 ninjia javascript这本书中,有对不能冒泡的特殊事件进行处理,以 change 事件为例,总结来讲就是

  • 实现一个 triggerEvent 方法,能手动触发事件
  • 如果目标元素不支持冒泡,那么使用其他的事件来监测子元素的 change 变化
  • 分别绑定 focusout click keydown beforeactivate 等监控函数
  • 当发现目标元素,比如 input,发生了值的变化,那么调用 triggerEvent
  • triggerEvent 会被递归的,冒泡调用
  • 如此,实现了冒泡不能冒泡的事件
  • 具体实现参见 Secrets of the JavaScript Ninja 这本书的 13.5 bubbling and delegation

pic1

实现方案二

anu.js 的作者在 blog中写道

对于focus,blur,change,submit,reset,select等不会冒泡的事件,在标准游览器中,我们可以设置addEventListener的最后一个参数为true轻松搞定

  • 巧妙的使用 addEventListener 的第3个捕获参数,那么首先事件就会在 root 被捕获
  • 然后获取到 e.target 也就是 input元素,然后再通过 input 元素,往上触发事件,实现冒泡
// 使用 capture 参数来实现捕获不能冒泡的事件
const el = document.querySelector('#el');
const ip = document.querySelector('#input');

el.addEventListener('blur', function(e) {
console.log(`#1 new ${e.target.value}`)
}, true); // blur 事件触发,将先打出 #1,再打出 #2

ip.addEventListener('blur', function(e) {
console.log(`#2 ${e.target.value}`)
})

比如在兼容 react 的框架 anu.js 中,对不能冒泡的 blur 事件是这样处理的:

pic2

  • react 事件是绑定到 document上的,所以 e.currentTarget 是 document,e.target 是 input
  • 根据 input,获取向上冒泡的路径,即会冒泡元素 collectPaths,然后一个循环触发,如果循环中有 stopPropagation,那么终止循环

当然这都不是 react 的实际实现,因为 React 的代码太难读了,盘根错节,我还没有找到具体实现在哪里。如有理解不正确,欢迎指出 ^_^。

1条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。