React 事件机制
原生事件和 React 事件监听方法
React
事件通过JSX
方式绑定的事件, 比如onClick={() => this.handle()}
- 原生事件使用
addEventListener
const ref = useRef()
const onClick = useCallback(() => {
}. []);
useEffect(() => {
// 绑定原生事件
ref.current.addEventListener('click', event => {});
}, []);
return (
<div
ref={ref}
onClick={onClick} // React 事件
/>
);
合成事件
如下代码 e
就是所谓的合成事件, 它并不是原生的一个 事件对象
, 而是 React
根据 W3C
规范定义出来的一个合成事件, 所以使用合成事件对象我们就不需要担心浏览器的兼容性问题了, 同时如果我们想要访问原生的事件对象, 可通过 nativeEvent
属性来获取
function Form() {
function handleSubmit(e) {
// e 为合成事件
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
PS:从
v0.14
开始, 事件处理函数, 返回false
时, 不再阻止事件传递, 这里需要手动调用e.stopPropagation()
或e.preventDefault()
作为替代方案
e.stopPropagation
:阻止事件冒泡,
e.preventDefault
:阻止默认行为,如阻止表单提交、链接跳转等
对原生事件的升级和改造
React 的合成事件是对原生事件的升级和改造。主要体现在对浏览器的兼容性处理
listen: function listen(target, eventType, callback) {
if (target.addEventistener) { // 非 ie,标准
target.addEventListener(eventType, callback, false)
return {
remove: function remove() {
target.removeEventListener(eventType, callback, false)
}
}
} else if (target.attachEvent) { // ie
target.attachEvent('on' + eventType, callback);
return {
remove: function remove() {
target.detachEvent('on' + eventType, callback)
}
}
}
}
事件注册机制
1.通过 事件委托
的方式,将所有时间都绑定在了 document
来进行统一处理
2.每次绑定都会将事件处理函数,存储起来
3.问: 对于同一个 DOM
分别绑定原生事件、合成事件,在原生事件中阻止事件冒泡为什么会阻止合成事件的执行?
合成事件是事件委托的一种实现,主要利用事件冒泡机制将所有事件在 document 上统一处理,根据事件流,事件执行顺序为 捕获阶段、目标阶段、冒泡阶段,当我们在原生事件上阻止事件冒泡,那么事件就无法冒泡到 document,那么合成事件自然无法执行
const ref = useRef()
const onClick = event => {
event.stopPropagation();
console.log('[ 合成事件 ]', event);
};
useEffect(() => {
ref.current.addEventListener('click', event => {
event.stopPropagation();
console.log('[ 原生事件 ]', event);
});
}, []);
return (
<div
ref={ref}
onClick={onClick}
/>
);
这里,会先执行原生事件,然后处理 React 事件原生事件(阻止冒泡)会阻止合成事件的执行,合成事件(阻止冒泡)不会阻止原生事件的执行,所以两者最好不要混合是用
4.问: React
为什么要将所有事件绑定在 document
上, 这么做有什么优缺点吗?
优点:
- 减少事件注册,减少内存消耗,提高性能
- 统一处理,并提供合成事件对象,抹平浏览器的兼容性差异
缺点:如果层级过多,冒泡过程中会被某层给阻止掉
5.从 v17.0.0
开始, React
不再将事件处理添加到 document
上, 而是将事件处理添加到渲染 React
树的根容器中这又是为什么呢?
如果页面上有多个 React 版本,事件都会被附加在
document
上,,这时嵌套的React
树调用e.stopPropagation()
停止了事件冒泡, 外部的树仍会接收到该事件(因为只是阻止了React
事件的冒泡), 这就使嵌套不同版本的React
难以实现如果你系统只用了一个
react
版本, 那没啥区别; 但有些复杂的系统, 由于历史原因, 或者用了微前端, 它就同时用很多个版本的react
, 这就不一样了, 如果很多个版本的react
, 都往document
上去绑定, 就容易出现混乱