国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務器之家 - 編程語言 - JavaScript - React - React事件機制源碼解析

React事件機制源碼解析

2022-02-25 16:26ZHANGYU React

這篇文章主要介紹了React事件機制源碼解析的相關資料,幫助大家更好的理解和學習使用React框架,感興趣的朋友可以了解下

React v17里事件機制有了比較大的改動,想來和v16差別還是比較大的。

本文淺析的React版本為17.0.1,使用ReactDOM.render創建應用,不含優先級相關。

原理簡述

React中事件分為委托事件(DelegatedEvent)和不需要委托事件(NonDelegatedEvent),委托事件在fiberRoot創建的時候,就會在root節點的DOM元素上綁定幾乎所有事件的處理函數,而不需要委托事件只會將處理函數綁定在DOM元素本身。

同時,React將事件分為3種類型——discreteEvent、userBlockingEvent、continuousEvent,它們擁有不同的優先級,在綁定事件處理函數時會使用不同的回調函數。

React事件建立在原生基礎上,模擬了一套冒泡和捕獲的事件機制,當某一個DOM元素觸發事件后,會冒泡到React綁定在root節點的處理函數,通過target獲取觸發事件的DOM對象和對應的Fiber節點,由該Fiber節點向上層父級遍歷,收集一條事件隊列,再遍歷該隊列觸發隊列中每個Fiber對象對應的事件處理函數,正向遍歷模擬冒泡,反向遍歷模擬捕獲,所以合成事件的觸發時機是在原生事件之后的。

Fiber對象對應的事件處理函數依舊是儲存在props里的,收集只是從props里取出來,它并沒有綁定到任何元素上。

源碼淺析

以下源碼僅為基礎邏輯的淺析,旨在理清事件機制的觸發流程,去掉了很多流程無關或復雜的代碼。

委托事件綁定

這一步發生在調用了ReactDOM.render過程中,在創建fiberRoot的時候會在root節點的DOM元素上監聽所有支持的事件。

?
1
2
3
4
5
6
7
8
9
10
11
12
function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // ...
  const rootContainerElement =
        container.nodeType === COMMENT_NODE ? container.parentNode : container;
  // 監聽所有支持的事件
  listenToAllSupportedEvents(rootContainerElement);
  // ...
}

listenToAllSupportedEvents

在綁定事件時,會通過名為allNativeEvents的Set變量來獲取對應的eventName,這個變量會在一個頂層函數進行收集,而nonDelegatedEvents是一個預先定義好的Set。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
  allNativeEvents.forEach(domEventName => {
    // 排除不需要委托的事件
    if (!nonDelegatedEvents.has(domEventName)) {
      // 冒泡
      listenToNativeEvent(
        domEventName,
        false,
        ((rootContainerElement: any): Element),
        null,
      );
    }
    // 捕獲
    listenToNativeEvent(
      domEventName,
      true,
      ((rootContainerElement: any): Element),
      null,
    );
  });
}

listenToNativeEvent

listenToNativeEvent函數在綁定事件之前會先將事件名在DOM元素中標記,判斷為false時才會綁定。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export function listenToNativeEvent(
  domEventName: DOMEventName,
  isCapturePhaseListener: boolean,
  rootContainerElement: EventTarget,
  targetElement: Element | null,
  eventSystemFlags?: EventSystemFlags = 0,
): void {
  let target = rootContainerElement;
    // ...
  // 在DOM元素上儲存一個Set用來標識當前元素監聽了那些事件
  const listenerSet = getEventListenerSet(target);
  // 事件的標識key,字符串拼接處理了下
  const listenerSetKey = getListenerSetKey(
    domEventName,
    isCapturePhaseListener,
  );
 
  if (!listenerSet.has(listenerSetKey)) {
    // 標記為捕獲
    if (isCapturePhaseListener) {
      eventSystemFlags |= IS_CAPTURE_PHASE;
    }
    // 綁定事件
    addTrappedEventListener(
      target,
      domEventName,
      eventSystemFlags,
      isCapturePhaseListener,
    );
    // 添加到set
    listenerSet.add(listenerSetKey);
  }
}

addTrappedEventListener

addTrappedEventListener函數會通過事件名取得對應優先級的listener函數,在交由下層函數處理事件綁定。

這個listener函數是一個閉包函數,函數內能訪問targetContainer、domEventName、eventSystemFlags這三個變量。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  // 根據優先級取得對應listener
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );
 
  if (isCapturePhaseListener) {
    addEventCaptureListener(targetContainer, domEventName, listener);
  } else {
    addEventBubbleListener(targetContainer, domEventName, listener);
  }
}

addEventCaptureListener函數和addEventBubbleListener函數內部就是調用原生的target.addEventListener來綁定事件了。

這一步是循環一個存有事件名的Set,將每一個事件對應的處理函數綁定到root節點DOM元素上。

不需要委托事件綁定

不需要委托的事件其中也包括媒體元素的事件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
export const nonDelegatedEvents: Set<DOMEventName> = new Set([
  'cancel',
  'close',
  'invalid',
  'load',
  'scroll',
  'toggle',
  ...mediaEventTypes,
]);
export const mediaEventTypes: Array<DOMEventName> = [
  'abort',
  'canplay',
  'canplaythrough',
  'durationchange',
  'emptied',
  'encrypted',
  'ended',
  'error',
  'loadeddata',
  'loadedmetadata',
  'loadstart',
  'pause',
  'play',
  'playing',
  'progress',
  'ratechange',
  'seeked',
  'seeking',
  'stalled',
  'suspend',
  'timeupdate',
  'volumechange',
  'waiting',
];

setInitialProperties

setInitialProperties方法里會綁定不需要委托的直接到DOM元素本身,也會設置style和一些傳入的DOM屬性。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
  rootContainerElement: Element | Document,
): void {
  let props: Object;
  switch (tag) {
    // ...
    case 'video':
    case 'audio':
      for (let i = 0; i < mediaEventTypes.length; i++) {
        listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
      }
      props = rawProps;
      break;
    default:
      props = rawProps;
  }
  // 設置DOM屬性,如style...
  setInitialDOMProperties(
    tag,
    domElement,
    rootContainerElement,
    props,
    isCustomComponentTag,
  );
}

switch里會根據不同的元素類型,綁定對應的事件,這里只留下了video元素和audio元素的處理,它們會遍歷mediaEventTypes來將事件綁定在DOM元素本身上。

listenToNonDelegatedEvent

listenToNonDelegatedEvent方法邏輯和上一節的listenToNativeEvent方法基本一致。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export function listenToNonDelegatedEvent(
  domEventName: DOMEventName,
  targetElement: Element,
): void {
  const isCapturePhaseListener = false;
  const listenerSet = getEventListenerSet(targetElement);
  const listenerSetKey = getListenerSetKey(
    domEventName,
    isCapturePhaseListener,
  );
  if (!listenerSet.has(listenerSetKey)) {
    addTrappedEventListener(
      targetElement,
      domEventName,
      IS_NON_DELEGATED,
      isCapturePhaseListener,
    );
    listenerSet.add(listenerSetKey);
  }
}

值得注意的是,雖然事件處理綁定在DOM元素本身,但是綁定的事件處理函數不是代碼中傳入的函數,后續觸發還是會去收集處理函數執行。

事件處理函數

事件處理函數指的是React中的默認處理函數,并不是代碼里傳入的函數。

這個函數通過createEventListenerWrapperWithPriority方法創建,對應的步驟在上文的addTrappedEventListener中。

createEventListenerWrapperWithPriority

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  // 從內置的Map中獲取事件優先級
  const eventPriority = getEventPriorityForPluginSystem(domEventName);
  let listenerWrapper;
  // 根據優先級不同返回不同的listener
  switch (eventPriority) {
    case DiscreteEvent:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case UserBlockingEvent:
      listenerWrapper = dispatchUserBlockingUpdate;
      break;
    case ContinuousEvent:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

createEventListenerWrapperWithPriority函數里返回對應事件優先級的listener,這3個函數都接收4個參數。

?
1
2
3
4
5
6
7
8
function fn(
  domEventName,
  eventSystemFlags,
  container,
  nativeEvent,
) {
  //...
}

返回的時候bind了一下傳入了3個參數,這樣返回的函數為只接收nativeEvent的處理函數了,但是能訪問前3個參數。

dispatchDiscreteEvent方法和dispatchUserBlockingUpdate方法內部其實都調用的dispatchEvent方法。

dispatchEvent

這里刪除了很多代碼,只看觸發事件的代碼。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): void {
  // ...
  // 觸發事件
  attemptToDispatchEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent,
  );
  // ...
}

attemptToDispatchEvent方法里依然會處理很多復雜邏輯,同時函數調用棧也有幾層,我們就全部跳過,只看關鍵的觸發函數。

dispatchEventsForPlugins

dispatchEventsForPlugins函數里會收集觸發事件開始各層級的節點對應的處理函數,也就是我們實際傳入JSX中的函數,并且執行它們。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // 收集listener模擬冒泡
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 執行隊列
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

extractEvents

extractEvents函數里主要是針對不同類型的事件創建對應的合成事件,并且將各層級節點的listener收集起來,用來模擬冒泡或者捕獲。

這里的代碼較長,刪除了不少無關代碼。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
): void {
  const reactName = topLevelEventsToReactNames.get(domEventName);
  let SyntheticEventCtor = SyntheticEvent;
  let reactEventType: string = domEventName;
    // 根據不同的事件來創建不同的合成事件
  switch (domEventName) {
    case 'keypress':
    case 'keydown':
    case 'keyup':
      SyntheticEventCtor = SyntheticKeyboardEvent;
      break;
    case 'click':
    // ...
    case 'mouseover':
      SyntheticEventCtor = SyntheticMouseEvent;
      break;
    case 'drag':
    // ...
    case 'drop':
      SyntheticEventCtor = SyntheticDragEvent;
      break;
    // ...
    default:
      break;
  }
  // ...
  // 收集各層級的listener
  const listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly,
  );
  if (listeners.length > 0) {
    // 創建合成事件
    const event = new SyntheticEventCtor(
      reactName,
      reactEventType,
      null,
      nativeEvent,
      nativeEventTarget,
    );
    dispatchQueue.push({event, listeners});
  }
}

accumulateSinglePhaseListeners

accumulateSinglePhaseListeners函數里就是在向上層遍歷來收集一個列表后面會用來模擬冒泡。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export function accumulateSinglePhaseListeners(
  targetFiber: Fiber | null,
  reactName: string | null,
  nativeEventType: string,
  inCapturePhase: boolean,
  accumulateTargetOnly: boolean,
): Array<DispatchListener> {
  const captureName = reactName !== null ? reactName + 'Capture' : null;
  const reactEventName = inCapturePhase ? captureName : reactName;
  const listeners: Array<DispatchListener> = [];
 
  let instance = targetFiber;
  let lastHostComponent = null;
 
  // 通過觸發事件的fiber節點向上層遍歷收集dom和listener
  while (instance !== null) {
    const {stateNode, tag} = instance;
    // 只有HostComponents有listener (i.e. <div>)
    if (tag === HostComponent && stateNode !== null) {
      lastHostComponent = stateNode;
 
      if (reactEventName !== null) {
        // 從fiber節點上的props中獲取傳入的事件listener函數
        const listener = getListener(instance, reactEventName);
        if (listener != null) {
          listeners.push({
            instance,
            listener,
            currentTarget: lastHostComponent,
          });
        }
      }
    }
    if (accumulateTargetOnly) {
      break;
    }
    // 繼續向上
    instance = instance.return;
  }
  return listeners;
}

最后的數據結構如下:

dispatchQueue的數據結構為數組,類型為[{ event,listeners }]。

這個listeners則為一層一層收集到的數據,類型為[{ currentTarget, instance, listener }]

processDispatchQueue

processDispatchQueue函數里會遍歷dispatchQueue。

?
1
2
3
4
5
6
7
8
9
10
export function processDispatchQueue(
  dispatchQueue: DispatchQueue,
  eventSystemFlags: EventSystemFlags,
): void {
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  for (let i = 0; i < dispatchQueue.length; i++) {
    const {event, listeners} = dispatchQueue[i];
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
  }
}

dispatchQueue中的每一項在processDispatchQueueItemsInOrder函數里遍歷執行。

processDispatchQueueItemsInOrder

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function processDispatchQueueItemsInOrder(
  event: ReactSyntheticEvent,
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean,
): void {
  let previousInstance;
  // 捕獲
  if (inCapturePhase) {
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  } else {
  // 冒泡
    for (let i = 0; i < dispatchListeners.length; i++) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  }
}

processDispatchQueueItemsInOrder函數里會根據判斷來正向、反向的遍歷來模擬冒泡和捕獲。

executeDispatch

executeDispatch函數里會執行listener。

?
1
2
3
4
5
6
7
8
9
10
function executeDispatch(
  event: ReactSyntheticEvent,
  listener: Function,
  currentTarget: EventTarget,
): void {
  const type = event.type || 'unknown-event';
  event.currentTarget = currentTarget;
  listener(event);
  event.currentTarget = null;
}

結語

本文旨在理清事件機制的執行,按照函數執行棧簡單的羅列了代碼邏輯,如果不對照代碼看是很難看明白的,原理在開篇就講述了。

React的事件機制隱晦而復雜,根據不同情況做了非常多的判斷,并且還有優先級相關代碼、合成事件,這里都沒有一一講解,原因當然是我還沒看~

平時用React也就寫寫簡單的手機頁面,以前老板還經常吐槽加載不夠快,那也沒啥辦法,就對我的工作而言,有沒有Cocurrent都是無關緊要的,這合成事件更復雜,完全就是不需要的,不過React的作者們腦洞還是牛皮,要是沒看源碼我肯定是想不到竟然模擬了一套事件機制。

小思考

  • 為什么原生事件的stopPropagation可以阻止合成事件的傳遞?

這些問題我放以前根本沒想過,不過今天看了源碼以后才想的。

  • 因為合成事件是在原生事件觸發之后才開始收集并觸發的,所以當原生事件調用stopPropagation阻止傳遞后,根本到不到root節點,觸發不了React綁定的處理函數,自然合成事件也不會觸發,所以原生事件不是阻止了合成事件的傳遞,而是阻止了React中綁定的事件函數的執行。
?
1
2
3
<div 原生onClick={(e)=>{e.stopPropagation()}}>
  <div onClick={()=>{console.log("合成事件")}}>合成事件</div>
</div>

比如這個例子,在原生onClick阻止傳遞后,控制臺連“合成事件”這4個字都不會打出來了。

以上就是React事件機制源碼解析的詳細內容,更多關于React事件機制源碼的資料請關注服務器之家其它相關文章!

原文鏈接:https://juejin.cn/post/6948726117591154719

延伸 · 閱讀

精彩推薦
  • ReactReact中setState的使用與同步異步的使用

    React中setState的使用與同步異步的使用

    這篇文章主要介紹了React中setState的使用與同步異步的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋...

    一顆冰淇淋5232022-02-17
  • React深入理解React Native核心原理(React Native的橋接(Bridge)

    深入理解React Native核心原理(React Native的橋接(Bridge)

    這篇文章主要介紹了深入理解React Native核心原理(React Native的橋接(Bridge),本文重點給大家介紹React Native的基礎知識及實現原理,需要的朋友可以參考下...

    Gavell9532022-02-23
  • ReactReactRouter的實現方法

    ReactRouter的實現方法

    這篇文章主要介紹了ReactRouter的實現,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下...

    WindrunnerMax6172022-01-06
  • ReactReact State狀態與生命周期的實現方法

    React State狀態與生命周期的實現方法

    這篇文章主要介紹了React State狀態與生命周期的實現方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考...

    一枚小棋子10852022-02-20
  • React聊一聊我對 React Context 的理解以及應用

    聊一聊我對 React Context 的理解以及應用

    這篇文章主要介紹了聊一聊我對 React Context 的理解以及應用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的...

    張國鈺6492022-02-24
  • React基于Vite 的組件文檔編寫神器,又快又省心

    基于Vite 的組件文檔編寫神器,又快又省心

    翻閱 Vite 的官方庫列表,偶然發現了一款 star 數量僅 100 多的文檔解決方案 vite-plugin-react-pages。開始用試試水的心態去去體驗一把,結果發現相當好用。...

    前端星辰9382021-12-27
  • ReactReact實現一個高度自適應的虛擬列表

    React實現一個高度自適應的虛擬列表

    這篇文章主要介紹了React如何實現一個高度自適應的虛擬列表,幫助大家更好的理解和學習使用React,感興趣的朋友可以了解下...

    抖音前端安全8882022-02-25
  • ReactReact+Antd 實現可增刪改表格的示例

    React+Antd 實現可增刪改表格的示例

    這篇文章主要介紹了React+Antd實現可增刪改表格的示例,幫助大家更好的理解和學習使用React,感興趣的朋友可以了解下...

    用戶3202285797825792022-02-24
主站蜘蛛池模板: 久久99久久99精品免视看婷婷 | 欧美激情区| 精品久久久久久久久久久下田 | www久久九| 性色aⅴ免费视频 | 久久亚洲视频 | 中文字幕国产 | 亚洲一区高清 | 久久久久久久成人 | 91精品久久久久久久久久 | 黄色一级毛片免费看 | 6080亚洲精品一区二区 | 亚洲一区二区在线播放 | 免费日韩一级片 | 天天操,夜夜操 | 免费一区二区三区 | 欧美日韩国产一区二区三区不卡 | 午夜精品一区二区三区在线播放 | 日韩电影网站 | 五月婷婷在线观看 | 午夜免费视频 | av网站免费在线观看 | 欧美一区二区三区在线 | 成人国产精品久久久 | 亚洲喷水 | 亚洲视频一区在线观看 | 日本久久网 | 97国产精品视频 | 精品国产子伦久久久久久小说 | 一本一道久久a久久精品逆3p | 国产精品成人在线观看 | 久久久www成人免费无遮挡大片 | 国产资源视频在线观看 | 99视频精品在线 | 中文字幕四区 | 亚洲欧美另类久久久精品2019 | 在线视频一区二区三区 | 久久久久国产一区二区三区四区 | 亚洲乱码日产精品一二三 | 欧美日韩在线看 | 日日麻批免费视频40分钟 |