react源码分析(3):react的事件委托机制
在开始之前,可以先看一下我的另一篇关于dom本身的事件机制《谈谈js点击之后发生了什么》
前言
如果你有试过输出react
事件中的event
,你就会发现这个event
好像和我们看到的dom
事件中的event
不太一样,那是因为react在进行dom事件绑定时,不是直接绑定事件的,而是通过所谓的合成事件(SyntheticEvent
)进行委托管理的,它是原生事件进行封装后的结果,你可以通过nativeEvent
获取原生事件。
通过例子观察
class App extends Component {
componentDidMount(){
document.addEventListener('click', function(){
console.log('document click')
})
document.getElementsByClassName('App')[0].addEventListener('click', function(){
console.log('app click')
})
document.getElementsByTagName('button')[0].addEventListener('click', function(e){
console.log('button click')
// e.stopPropagation();
})
}
onClick = (e) => {
e.stopPropagation() // 能够阻止div.app的触发
e.nativeEvent.stopImmediatePropagation(); // 能够阻止document的触发
e.nativeEvent.stopPropagation(); // 什么都阻止不了
console.log('react button click');
};
render() {
return (
<div className="App" onClick={() => {console.log('react app click')}}>
<button onClick={this.onClick}>按钮</button>
</div>
);
}
}
我们在用react绑定了两个事件,同时在didmount
给真实dom也绑定了事件,点击button之后的执行顺序是
button click
app click
react button click
react app click
document click
原理
react并不是我之前所设想的将事件绑定在真实dom上,而是通过自己的事件处理器来处理,将所有的事件都绑定在document上,这样真实点击的时候,冒泡到document上,react再通过document去dispatchEvent统一处理事件
所以上面的
stopPropagation
就能理解了,e.stopPropagation
只能阻止虚拟dom的事件冒泡,但它本身是由document触发的,所以e.nativeEvent.stopPropagation
什么也阻止不了,document
就是冒泡的顶点,e.nativeEvent.stopImmediatePropagation
可以阻止document
的事件,这和它本身有关
需要注意的是react
在生成真实的dom节点会加入一些东西帮助事件分发
dom节点的react属性(15和16有点区别)
15和16会有一点细微的区别,具体什么还没有去了解,但是看来对事件的影响不大,15是有一个__rootNodeID
来区分组件,16是通过__debugID
来区分,如果理解上有错误,欢迎指正哦
- vdom
// 简化
let vdom = {
type: 'div',
props: {
onClick: function(){
console.log('react app click')
},
children: [
{
type: 'button',
props: {
onClick: function() {
console.log('react button click')
}
}
}
]
}
}
- 注册事件
let bankForRegistrationName = {}; // 回调事件的保存
// react构建真实dom树
...
// 注册事件
bankForRegistrationName = {
// 数字是_debugID,react用于识别每一个dom
5: {
click: function(){
console.log('react app click')
},
},
6: {
click: function(){
console.log('react button click')
},
}
}
- 事件触发
// 合成事件简单实现
function SyntheticEvent(e) {
...
this.nativeEvent = e;
...
}
// e: event, type: 事件类型
function dispatchEvent(e, type) {
let synE = new SyntheticEvent(e);
// 执行监听事件
let debugID = e.target.__reactInternalInstance$om8tco7dvl._debugID;
bankForRegistrationName[debugID][type](synE);
}
// document事件委托
document.addEventListener('click', function(e) {
dispatchEvent(e, 'click');
})
总结
基本上就是这样了(当然没有对冒泡做处理,react会遍历自己的vdom去执行冒泡
)
- 事件管理中心(
bankForRegistrationName
)会在react-render
过程中保存所有所有dom事件
- document作为事件委托者,用来分发事件(
dispatchEvent
),通过dom节点唯一标识(_debugID
)去事件管理(bankForRegistrationName
)触发事件