说说React事件和原生事件的执行顺序

374 阅读4分钟

在 React 中,事件处理机制与原生 DOM 事件有所不同。React 实现了一套自己的合成事件系统(Synthetic Event),它是对原生 DOM 事件的一层封装。理解 React 事件和原生事件的执行顺序对于调试和优化应用程序非常重要。


1. React 合成事件系统

React 的合成事件系统主要有以下特点:

  • 跨浏览器兼容性:React 事件在不同浏览器中表现一致。
  • 事件委托:React 将所有事件委托到文档的根节点(documentroot),而不是直接绑定到具体的 DOM 元素。
  • 性能优化:通过事件池(event pooling)复用事件对象,减少内存开销。

2. 事件执行顺序

React 事件和原生事件的执行顺序取决于事件的绑定方式和传播阶段(捕获阶段和冒泡阶段)。

事件传播阶段:

  1. 捕获阶段(Capture Phase):事件从根节点向下传播到目标节点。
  2. 目标阶段(Target Phase):事件到达目标节点。
  3. 冒泡阶段(Bubble Phase):事件从目标节点向上传播到根节点。

执行顺序:

  1. 原生事件的捕获阶段:如果原生事件绑定了捕获阶段的监听器,会先执行。
  2. React 事件的捕获阶段:如果 React 事件绑定了捕获阶段的监听器,会接着执行。
  3. 原生事件的目标阶段:执行目标节点上的原生事件监听器。
  4. React 事件的目标阶段:执行目标节点上的 React 事件监听器。
  5. 原生事件的冒泡阶段:执行原生事件的冒泡阶段监听器。
  6. React 事件的冒泡阶段:执行 React 事件的冒泡阶段监听器。

3. 代码示例

以下代码演示了 React 事件和原生事件的执行顺序:

import React, { useEffect } from 'react';

function App() {
  useEffect(() => {
    const parent = document.getElementById('parent');
    const child = document.getElementById('child');

    // 原生事件 - 捕获阶段
    parent.addEventListener('click', () => {
      console.log('原生事件 - 父元素捕获');
    }, true);

    // 原生事件 - 冒泡阶段
    parent.addEventListener('click', () => {
      console.log('原生事件 - 父元素冒泡');
    }, false);

    // 原生事件 - 捕获阶段
    child.addEventListener('click', () => {
      console.log('原生事件 - 子元素捕获');
    }, true);

    // 原生事件 - 冒泡阶段
    child.addEventListener('click', () => {
      console.log('原生事件 - 子元素冒泡');
    }, false);
  }, []);

  const handleParentClickCapture = () => {
    console.log('React 事件 - 父元素捕获');
  };

  const handleParentClickBubble = () => {
    console.log('React 事件 - 父元素冒泡');
  };

  const handleChildClickCapture = () => {
    console.log('React 事件 - 子元素捕获');
  };

  const handleChildClickBubble = () => {
    console.log('React 事件 - 子元素冒泡');
  };

  return (
    <div
      id="parent"
      onClick={handleParentClickBubble}
      onClickCapture={handleParentClickCapture}
    >
      <div
        id="child"
        onClick={handleChildClickBubble}
        onClickCapture={handleChildClickCapture}
      >
        点击我
      </div>
    </div>
  );
}

export default App;

输出结果:

  1. 原生事件 - 父元素捕获
  2. React 事件 - 父元素捕获
  3. 原生事件 - 子元素捕获
  4. React 事件 - 子元素捕获
  5. 原生事件 - 子元素冒泡
  6. React 事件 - 子元素冒泡
  7. 原生事件 - 父元素冒泡
  8. React 事件 - 父元素冒泡

4. 关键点总结

  1. 捕获阶段优先于冒泡阶段

    • 无论是原生事件还是 React 事件,捕获阶段都会先于冒泡阶段执行。
  2. 原生事件优先于 React 事件

    • 在同一阶段(捕获或冒泡),原生事件的监听器会先于 React 事件的监听器执行。
  3. React 事件委托到根节点

    • React 将所有事件委托到根节点(documentroot),而不是直接绑定到具体的 DOM 元素。
  4. 事件池机制

    • React 的合成事件对象会被复用,因此在异步代码中访问事件对象时,需要调用 event.persist() 来保留事件对象。

5. 注意事项

  • 避免混用原生事件和 React 事件
    • 如果同时使用原生事件和 React 事件,可能会导致事件处理逻辑混乱,增加调试难度。
  • 阻止事件传播
    • 在 React 事件中调用 event.stopPropagation() 只会阻止 React 事件的传播,不会影响原生事件。如果需要完全阻止事件传播,需要同时处理原生事件和 React 事件。

6. 总结

  • React 合成事件系统是对原生事件的封装,提供了跨浏览器兼容性和性能优化。
  • 事件执行顺序遵循捕获阶段优先于冒泡阶段,原生事件优先于 React 事件。
  • 理解事件执行顺序有助于更好地调试和优化 React 应用程序。