通過一道題來看React事件模型

IOneStar 發表於 2022-06-20
React

下面程式碼輸出什麼

const MainApp = () => {
  const parentRef = useRef();
  const childRef = useRef();
  const parentClickFun = useCallback(() => {
    console.log('react parent');
  }, []);
  const childClickFun = useCallback(() => {
    console.log('react child');
  }, []);
  useEffect(() => {
    document.addEventListener('click', () => {
      console.log('document');
    });
    parentRef.current?.addEventListener('click', () => {
      console.log('dom parent');
    });
    childRef.current?.addEventListener('click', () => {
      console.log('dom child');
    });
  }, []);
  return (
    <div ref={parentRef} onClick={parentClickFun}>
      <div ref={childRef} onClick={childClickFun}>
        事件執行順序
      </div>
    </div>
  );
};

執行結果:

  • dom child
  • dom parent
  • react child
  • react parent
  • document

    程式碼分析

主要是考察 React 合成事件和 JS 原生事件的區別,以及它們的執行順序。以React16.x版本之前的來分析。React16.x 以後有變更。
分析一下上面的程式碼:可以分成兩部分來看,JS原生事件部分及React合成事件部分。

  • useEffect 裡面都是直接通過 addEventListener 做的事件繫結,如果addEventListener 不指定第二個引數的話,預設是冒泡階段執行。所以 useEffect裡的執行順序 dom child, dom parent, document
  • 再來看 parentClickFun, childClickFun 這兩個函式是通過 React 的事件去繫結的。React 利用事件委託機制在 document 上統一監聽DOM事件,再根據觸發的target將事件分發到具體的元件例項。React 自己實現了一套冒泡機制。所以這部分的執行順序為:react child,react parent

最後來理解一下整個的執行順序。

  • react中的所有事件都是掛載到document上的
  • 當真實 dom 觸發冒泡到 document 後才會對react事件進行處理
  • 所以JS原生事件會先執行
  • 然後執行React的合成事件
  • 最後執行真正掛載到 document上的事件

更多可以檢視下面提到的文章

參考