React基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等。
合成事件是 React模拟原生 DOM事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。根据 W3C规范来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。


由此可见,React中的event是syntheticEvent,模拟出来了Dom事件所有的能力。而event.nativeEvent才是原生事件对象。


由此可见,event.nativeEvent.target是当前触发元素,而event.nativeEvent.currentTarget是绑定事件的元素。
React事件注册过程其实主要做了2件事:事件注册、事件存储。
事件注册 : 组件挂载阶段,根据组件内的声明的事件类型onclick,onchange 等,给 document 上添加事件 addEventListener,并指定统一的事件处理程序 dispatchEvent。
事件存储 :就是把 react 组件内的所有事件统一的存放到一个对象里,缓存起来,为了在触发事件的时候可以查找到对应的方法去执行。
enqueuePutListener(this, propKey, nextProp, transaction);
在这个方法里会进行事件的注册以及事件的存储,包括冒泡和捕获的处理。
根据当前的组件实例获取到最高父级——也就是document,然后执行方法 listenTo -——也是最关键的一个方法,进行事件绑定处理。最后执行 EventListener.listen(冒泡)或者 EventListener.capture(捕获),单看下冒泡的注册,其实就是 addEventListener的第三个参数是 false。到这里事件注册就完事儿了。
开始事件的存储,在 react 里所有事件的触发都是通过 dispatchEvent方法统一进行派发的,而不是在注册的时候直接注册声明的回调,来看下如何存储的 。
react 把所有的事件和事件类型以及react 组件进行关联,把这个关系保存在了一个 map里,也就是一个对象里(键值对),然后在事件触发的时候去根据当前的 组件id和 事件类型查找到对应的 事件fn。
这里的组件 id 就是组件的唯一标识,然后和fn进行关联,在触发阶段就可以找到相关的事件回调。
import React from 'react';
class App extends React.Component{
constructor(props) {
super(props);
this.parentRef = React.createRef();
this.childRef = React.createRef();
}
componentDidMount() {
console.log("React componentDidMount!");
this.parentRef.current?.addEventListener("click", () => {
console.log("原生事件:父元素 DOM 事件监听!");
});
this.childRef.current?.addEventListener("click", () => {
console.log("原生事件:子元素 DOM 事件监听!");
});
document.addEventListener("click", (e) => {
console.log("原生事件:document DOM 事件监听!");
});
}
parentClickFun = () => {
console.log("React 事件:父元素事件监听!");
};
childClickFun = () => {
console.log("React 事件:子元素事件监听!");
};
render() {
return (
<div ref={this.parentRef} onClick={this.parentClickFun}>
<div ref={this.childRef} onClick={this.childClickFun}>
分析事件执行顺序
</div>
</div>
);
}
}
export default App;
输出顺序为:
原生事件:子元素 DOM 事件监听!
原生事件:父元素 DOM 事件监听!
React 事件:子元素事件监听!
React 事件:父元素事件监听!
原生事件:document DOM 事件监听!
可以得到以下结论:
document 对象上。document 对象后,再处理 React 事件。所以想要阻止不同时间段的冒泡行为,对应使用不同的方法,对应如下:
e.nativeEvent.stopImmediatePropagation();