Skip to content
On this page

3. React18 - 事件注册和派发


事件执行过程

html
<div id="root">
  <div id="parent">
    <div id="child">点击</div>
  </div>
</div>

模拟过程

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible"
        content="IE=edge">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0">
  <title>了解事件捕获和冒泡过程</title>
</head>

<body>
  <div id="root">
    <div id="parent">
      <div id="child">点击</div>
    </div>
  </div>
</body>
<script>
  var parentBubble = () => {
    console.log('父React冒泡');
  }

  var parentCapture = () => {
    console.log('父React捕获');
  }

  var childBubble = () => {
    console.log('子React冒泡');
  }

  var childCapture = () => {
    console.log('子React捕获');
  }

  let root = document.getElementById('root');
  let parent = document.getElementById('parent');
  let child = document.getElementById('child');
  parent.onClick = parentBubble;
  parent.onClickCapture = parentCapture;
  child.onClick = childBubble;
  child.onClickCapture = childCapture;
  root.addEventListener('click', (event) => dispatchEvent(event, true), true);
  root.addEventListener('click', (event) => dispatchEvent(event, false), false);

  function dispatchEvent(event, isCapture) {
    let currentTarget = event.target;
    //说明:从内往外寻找dom节点直到根
    let paths = []; //[child,parent,root,body,html,document]
    while (currentTarget) {
      paths.push(currentTarget);
      currentTarget = currentTarget.parentNode;
    }
    if (isCapture) {
      //捕获阶段: 是从外往内进行依次捕获
      for (let i = paths.length - 1; i >= 0; i--) {
        let handler = paths[i].onClickCapture;
        handler && handler();
      }
    } else {
      //冒泡阶段: 是从内往外进行依次冒泡
      for (let i = 0; i < paths.length; i++) {
        let handler = paths[i].onClick;
        handler && handler();
      }
    }
  }
  parent.addEventListener('click', () => {
    console.log('父原生捕获');
  }, true);
  parent.addEventListener('click', () => {
    console.log('父原生冒泡');
  }, false);
  child.addEventListener('click', () => {
    console.log('子原生捕获');
  }, true);
  child.addEventListener('click', () => {
    console.log('子原生冒泡');
  }, false);
</script>

</html>

日志输出:

父React捕获
子React捕获
父原生捕获
子原生捕获
子原生冒泡
父原生冒泡
子React冒泡
父React冒泡

示例

jsx
import { createRoot } from "react-dom/client";
function FunctionComponent() {
  return (
    <h1
      onClick={(event) => console.log(`ParentBubble`)}
      onClickCapture={(event) => {
        console.log(`ParentCapture`)
        event.stopPropagation();
      }}
    >
      <span
        onClick={(event) => {
          console.log(`ChildBubble`);
          event.stopPropagation();
        }}
        onClickCapture={(event) => console.log(`ChildCapture`)}
      >world</span>
    </h1>
  )
}
let element = <FunctionComponent />
const root = createRoot(document.getElementById("root"));
root.render(element);

事件注册和派发流程

完整流程图:https://app.diagrams.net/#G1ugEBXMgklPNhU39Vunn4mN-JhEi-hZQf#{"pageId"%3A"Sdm2yIqwJ5qRPqILZMSH"}

总结

  1. 创建root 真实节点开始,就将事件委托至根root节点上
  2. 包装捕获和冒泡阶段事件Listener至root节点上
    1. 通过插件事件中心来注册事件和处理事件
    2. 点击dom节点出发事件:根据点击的真实DOM的fiber,往上依次寻找有stateNode的fiber.props中的onClick 和onClickCapture事件,将所有listener和合成事件event 放进dispatchQueue中
    3. 事件处理顺序
      • 捕获阶段:从外往内(先父后子)依次捕获,依次执行dispatchQueue中的listener(倒序,因为:队列是先子后父)
      • 冒泡阶段:从内往外(先子后父)依次冒泡,,依次执行dispatchQueue中的listener(正序)

Released under the MIT License.