写在前面
本文源于本人在学习react
过程中遇到的一个问题;本文内容为本人的一些的理解,如有不对的地方,还请大家指出来。本文是讲react的事件,不是介绍其api,而是猜想一下react
合成事件的实现方式
遇到的问题
class EventTest extends Component {
handleParentClick(e) {
console.log('click parent div');
}
handleChildClick(e) {
e.stopPropagation();
console.log('click child div');
}
componentDidMount() {
document.querySelector('.parent').addEventListener('click', this.handleParentClick);
}
render() {
return (
<div className="parent">
<div className="child" onClick={this.handleChildClick}></div>
</div>
);
}
}
上述代码render
出来后,尝试点击一下div.child
,诡异的现象产生了:
控制台中输出如上图所示,这完全不符合浏览器的事件执行啊,我所期望的是指输出click child div
,因为已经利用了e.stopPropagation()
来阻止冒泡,说明阻止冒泡失效了,但是仅仅如此吗,可以发现的是首先输出的是click parent div
(wtf)。
解决问题
为了解决上述问题,先来了解下react
的事件,react
事件是合成事件,为原生事件的一个子集,仅仅是进行了一个跨浏览器的封装。但是真的只有这么简单?图样图森破。
利用控制台,看下div.child
对应的事件处理函数:
一个空函数,事件的监听函数不是所定义的handleChildClick
,而是emptyFunction
,也就是说react
没有在真实的DOM
节点上绑定事件(在DOM
节点上绑定事件比较消耗内存,因为当dom
节点被remove
后,虽然不存在与dom tree
中,但是仍存在与内存中,需要手动remove
事件orchild = null
),react
的合成事件利用的是事件代理方式实现,也就是说会将事件监听器绑定到整个文档document
上,是不是这样呢?来验证一下,利用chrome:
可以发现,document
上的确被绑定了click
事件,dom
节点的真实的事件处理函数全部以一个特定的结构存储在了内存中,当点击div.child
时,这时其事件处理函数为emptyFunction
,执行这个函数无任何作用,按照浏览器标准事件模型,开始向上冒泡,这时到了div.parent
,于是输出了click parent div
,一直向上到了document
上,这时根据e.target
进行处理,而react
并不会根据dom
层级式传播那样遍历virtual dom
结构,这样有时遍历的层级会很多,而且会有很多的无效遍历。
react
是怎么做的呢?
react
依靠每个React component
各自独立的id
来编码这个层级。这样就能通过简单的字符串操作来获取所有父级 component 的父级内容,再把事件监听存储在hashmap当中,比如有如下结构并且为没一层div
添加onClick
div.a
div.b
div.c
当点击div.c
时,处理方式:
clickBubbleListeners['a.b.c'](event);
clickBubbleListeners['a.b'](event);
clickBubbleListeners['a'](event);
在合成事件中用e.stopPropagation
只能阻断上述冒泡过程。
结论
由此可以看出:
阻止
react
事件冒泡的行为只能用于react
合成事件中,对于原生事件无效(合成事件中的e.stopPropagation
与原生事件中的e.stopPropagation
并不是一回事)阻止原生事件的冒泡行为,可以阻止
react
合成事件的传播(根本不会冒泡到document
上,所以不会触发react
的合成事件)在写
react
时,最好不要将合成事件与原生事件混用。
参考
本文部分参考自IMWeb—React事件初探
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。