本文作者: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
.
另一方面,从性能上来讲,捕获模型的性能会好一丢丢,见 这里的讨论.
前面是铺垫,现在引入主题。
有一个问题一直困惑我:有些事件是不支持事件冒泡的,比如 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 事件为例,总结来讲就是
anu.js 的作者在 blog中写道:
对于focus,blur,change,submit,reset,select等不会冒泡的事件,在标准游览器中,我们可以设置addEventListener的最后一个参数为true轻松搞定
// 使用 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 事件是这样处理的:
collectPaths
,然后一个循环触发,如果循环中有 stopPropagation,那么终止循环当然这都不是 react 的实际实现,因为 React 的代码太难读了,盘根错节,我还没有找到具体实现在哪里。如有理解不正确,欢迎指出 ^_^。