寫給進階玩家的 React 事件系統原理

【 簡介 】

React 合成事件是 React 模擬原生 DOM 事件所有能力的一個對象,它根據 W3C 規範來定義合成事件,兼容所有瀏覽器擁有與瀏覽器原生事件相同的接口。

react 官方描述分別打印出合成事件對象 e 和原生對象 e.nativeEvent

【 React 事件系統架構 】

_【 核心代碼 】// _我們可以將 react 系統分成註冊和執行兩部分去理解:

一、註冊:

// 
function enqueuePutListener(inst, registrationName, listener, transaction) {
    ...
  var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
  1. 找到document
  var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
  2. 註冊事件,將事件註冊到document上
  3. listenTo(registrationName, doc);
  存儲事件,放入事務隊列中
}

react 事件系統內部對事件在不同瀏覽器上的執行做了兼容因此無需使用者考慮瀏覽器相關情況:

  listen: function listen(target, eventType, callback) {
    if (target.addEventListener) {
      將原生事件添加到target這個dom上,也就是上邊傳遞的document上
      這就是隻有document這個DOM節點上有原生事件的原因
      target.addEventListener(eventType, callback, false);
      ...
    } else if (target.attachEvent) {
      target.attachEvent('on' + eventType, callback);
      ...
    }
  }

二、執行:

事件分發 dispatchEvent(react 合成事件的冒泡機制)

function handleTopLevelImpl(bookKeeping) {
  1. 找到事件觸發的DOM和React Component
  var nativeEventTarget = getEventTarget(bookKeeping.nativeEvent);
  var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode(nativeEventTarget);

  2. 執行事件回調前,先由當前組件向上遍歷它的所有父組件。
  得到ancestors這個數組,這個數組同時也是冒泡順序。
  var ancestor = targetInst;
  do {
    bookKeeping.ancestors.push(ancestor);
    ancestor = ancestor && findParent(ancestor);
  } while (ancestor);

  這個順序就是冒泡的順序,並且我們發現不能通過stopPropagation來阻止'冒泡'。
  for (var i = 0; i < bookKeeping.ancestors.length; i++) {
    targetInst = bookKeeping.ancestors[i];
    ReactEventListener._handleTopLevel(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
  }
}

handleTopLevel 構造合成事件並執行(依賴 EventPluginHub)

1. 初始化時將eventPlugin註冊到EventPluginHub中,不同plugin分別構造不同類型的合成事件
  ReactInjection.EventPluginHub.injectEventPluginsByName({
    ...不同插件
  });
  
2. 將事件放入事件池:
   EventPluginHub.enqueueEvents(events);
3. 再處理隊列中的事件,包括之前未處理完的:
   EventPluginHub.processEventQueue(false);

【 合成事件、原生事件混用 demo】

剛接觸 react 的同學,往往在 react 事件使用時會與原生事件混合(這裏並非指責這種混用行爲,只是在混用階段需要區分出 react 時間系統和 js 本身事件的執行差異),時常會有事件執行出現不符合預期的情況,這裏我們用一個小 demo 來感受下二者執行的差異:打印出執行結果:

由此我們可以瞭解到如下原生事件、react 事件、document 上掛載事件執行順序:

【 合成事件存在意義 】

1. 統一管理(document)

react 事件集中在 document 上集中管理

2. 不同瀏覽器兼容問題

3. 減少事件創建銷燬的性能損耗(避免頻繁的垃圾回收機制)

react 事件隊列的存儲和取出使用緩解了 dom 元素註冊銷燬所消耗的性能

4. 利用合成事件的冒泡從 document 中觸發的特性

【 合成事件中存在的問題 】

1. 原生事件和合成事件混用時原生事件對入 react 合成事件的影響

2. 事件池中中事件處理函數全部被調用之後,所有屬性都會被置爲 null

避免方法:

3. 不同版本的 React 組件嵌套使用時,e.stopPropagation 無法正常工作(兩個不同版本的事件系統是獨立的,都註冊到 document 時的時間不相同)

【 相關參考 】

參考資料

[1]

https://github.com/facebook/react/: https://github.com/facebook/react/

[2]

React 17 要來了,非常特別的一版 - 夢燼 - 博客園: https://www.cnblogs.com/ayqy/p/react-17.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/mkus1HCB1B6sQ4utb5i39w