React 合成事件 SyntheticEvent

合成事件在 react 中的機(jī)制

1.原生事件冒泡到 document
2.document 執(zhí)行事件監(jiān)聽回調(diào),把原生事件在 dispatchEvent 里派發(fā)合成事件
3.通過 event.target 找到組件和元素
4.dispatchEvent 中執(zhí)行 batchUpdate batchUpdate (fn, a) => fn(a), fn 是組件元素綁定的方法,a 是 event 合成事件實(shí)例
5.react 會(huì)在事件池 eventPool 中重復(fù)使用 event 實(shí)例。

React事件初探

為什么要用合成事件

  1. 瀏覽器兼容,頂層事件代理機(jī)制,能報(bào)保證事件冒泡一致性,可以跨瀏覽器執(zhí)行
  2. 更好的跨平臺(tái),不同平臺(tái)事件模擬成合成事件
  3. document 事件代理只在內(nèi)存中開辟了一塊空間,節(jié)省資源同時(shí)減少了dom操作,提高性能
  4. 對于新添加的元素也會(huì)有之前的事件
  5. 避免頻繁解綁, 只在組件銷毀時(shí)解綁
  6. 方便事件的統(tǒng)一管理和事務(wù)機(jī)制

react 合成事件流程

合成事件在 react 中的表現(xiàn)

合成事件對象模擬了 event.preventDefault event.stopPropagation 方法,同時(shí)為了提高性能在事件池重復(fù)使用 event 對象,每次重復(fù)使用后都會(huì)把 event 對象信息清空,在 setState 和異步 api 中可以使用 event.persist 方法或暫存值 onChange={({ value }) => handle(value)} 的方式獲取正確的屬性。
Synthetic Events in React
event.persist()
React SyntheticEvent reuse
官方 event-pooling 應(yīng)用示例

合成事件源碼分析

dispatch 分發(fā)事件

頂層監(jiān)聽

export function trapBubbledEvent(
  topLevelType: DOMTopLevelEventType,
  element: Document | Element,
) {
  if (!element) {
    return null;
  }
  const dispatch = isInteractiveTopLevelEventType(topLevelType)
    ? dispatchInteractiveEvent
    : dispatchEvent;
  // 原生 dom 事件監(jiān)聽
  addEventBubbleListener(
    element, // document 對象
    getRawEventName(topLevelType),
    // Check if interactive and wrap in interactiveUpdates
    dispatch.bind(null, topLevelType),
  );
}

dispatchEvent, 這里的 bookkeeping 是重用的,與 eventpool 重用相似。

export function dispatchEvent(
  topLevelType: DOMTopLevelEventType,
  nativeEvent: AnyNativeEvent,
) {
  if (!_enabled) {
    return;
  }

  const nativeEventTarget = getEventTarget(nativeEvent); // 找到 event.target 觸發(fā)事件的元素
  let targetInst = getClosestInstanceFromNode(nativeEventTarget); // 找到 fiber 實(shí)例
  if (
    targetInst !== null &&
    typeof targetInst.tag === 'number' &&
    !isFiberMounted(targetInst)
  ) {
    // If we get an event (ex: img onload) before committing that
    // component's mount, ignore it for now (that is, treat it as if it was an
    // event on a non-React tree). We might also consider queueing events and
    // dispatching them after the mount.
    targetInst = null;
  }
  // 復(fù)用 bookKeeping,保存了事件觸發(fā)的相關(guān)實(shí)例信息
  const bookKeeping = getTopLevelCallbackBookKeeping(
    topLevelType,
    nativeEvent,
    targetInst,
  );

  try {
    // Event queue being processed in the same cycle allows
    // `preventDefault`.
    batchedUpdates(handleTopLevel, bookKeeping); // 批量處理的方式進(jìn)行分發(fā)
  } finally {
    releaseTopLevelCallbackBookKeeping(bookKeeping); // 推入 pool
  }
}

batchedUpdates(handleTopLevel, bookKeeping); 中的 handleTopLevel

// EventPluginHub.js
function handleTopLevel(bookKeeping) {
  let targetInst = bookKeeping.targetInst;
  // ... 確定 bookKeeping 上的組件信息
  for (let i = 0; i < bookKeeping.ancestors.length; i++) {
    targetInst = bookKeeping.ancestors[i];
    runExtractedEventsInBatch(  // 在觸發(fā)的組件實(shí)例上執(zhí)行批量事件
      bookKeeping.topLevelType,
      targetInst,
      bookKeeping.nativeEvent,
      getEventTarget(bookKeeping.nativeEvent), // 統(tǒng)一不同瀏覽器的事件名
    );
  }
}

runExtractedEventsInBatch 最終會(huì)執(zhí)行到 executeDispatchesAndRelease 方法

/**
 * Dispatches an event and releases it back into the pool, unless persistent.
 * dispatch 事件并將其釋放回池中,除非是持久的。
 * @param {?object} event Synthetic event to be dispatched.
 * @param {boolean} simulated If the event is simulated (changes exn behavior)
 * @private
 */
const executeDispatchesAndRelease = function(
  event: ReactSyntheticEvent,
  simulated: boolean,
) {
  if (event) {
    executeDispatchesInOrder(event, simulated);
    // 如果合成事件沒有 persist , 才推入到 eventPool 中進(jìn)行復(fù)用
    if (!event.isPersistent()) {
      event.constructor.release(event);
    }
  }
};

SyntheticEvent 合成事件對象

SyntheticEvent.js

function SyntheticEvent(
  dispatchConfig,
  targetInst,
  nativeEvent,
  nativeEventTarget,
) {
  // ...
  persist: function() {
    this.isPersistent = functionThatReturnsTrue;
  },

  isPersistent: functionThatReturnsFalse,
// ...
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
  const EventConstructor = this;
  if (EventConstructor.eventPool.length) { // 重用合成事件
    const instance = EventConstructor.eventPool.pop();
    EventConstructor.call(
      instance,
      dispatchConfig,
      targetInst,
      nativeEvent,
      nativeInst,
    );
    return instance;
  }
  return new EventConstructor( // 如何合成事件持久化了則創(chuàng)建新的合成事件
    dispatchConfig,
    targetInst,
    nativeEvent,
    nativeInst,
  );
}
// 只在未持久化 isPersistent 為 false 才用到
function releasePooledEvent(event) {
  const EventConstructor = this;
  invariant(
    event instanceof EventConstructor,
    'Trying to release an event instance into a pool of a different type.',
  );
  event.destructor(); // 重置合成事件,屬性全設(shè)置為 null
  if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
    EventConstructor.eventPool.push(event); //
  }
}

function addEventPoolingTo(EventConstructor) {
  EventConstructor.eventPool = []; // 事件池
  EventConstructor.getPooled = getPooledEvent; // 獲取事件池事件進(jìn)行使用和復(fù)用
  EventConstructor.release = releasePooledEvent; // 發(fā)布事件到事件池中
}
}
export default SyntheticEvent;

總結(jié)

合成事件是 react 模擬原生 dom 事件所有能力的一個(gè)事件對象,用于兼容瀏覽器方便 react 統(tǒng)一管理。

react 合成事件是通過模擬不同瀏覽器事件差異,頂層監(jiān)聽在 document 上保證了事件冒泡的統(tǒng)一性。

當(dāng)事件原生 dom 事件觸發(fā)冒泡至 document 時(shí),react 通過 event.target 找到事件觸發(fā)的組件實(shí)例,并 dispatchEvent 派發(fā)合成事件 event ,把 event 通過 batchUpdates 交由綁定事件的處理函數(shù)。

react 會(huì)重復(fù)使用合成 event,如果 event 已經(jīng) persisted 則不會(huì)推入 eventPool 中每次處理 handle 時(shí)都會(huì)重新生成一個(gè) event。

react 合成事件兼容模擬瀏覽器事件差異,使用事件代理方式節(jié)省了內(nèi)存只開辟一塊空間,在組件銷毀時(shí)解綁,避免頻繁解綁方便事件的統(tǒng)一管理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容