Skip to content

React 事件机制

原生事件和 React 事件监听方法

  • React 事件通过 JSX 方式绑定的事件, 比如 onClick={() => this.handle()}
  • 原生事件使用 addEventListener
jsx
const ref = useRef()
const onClick = useCallback(() => {
}. []);

useEffect(() => {
  // 绑定原生事件
  ref.current.addEventListener('click', event => {});
}, []);

return (
  <div
    ref={ref}
    onClick={onClick} // React 事件
  />
);

合成事件

如下代码 e 就是所谓的合成事件, 它并不是原生的一个 事件对象, 而是 React 根据 W3C 规范定义出来的一个合成事件, 所以使用合成事件对象我们就不需要担心浏览器的兼容性问题了, 同时如果我们想要访问原生的事件对象, 可通过 nativeEvent 属性来获取

jsx
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 的合成事件是对原生事件的升级和改造。主要体现在对浏览器的兼容性处理

javascript
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,那么合成事件自然无法执行

javascript
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 上去绑定, 就容易出现混乱

参考资料