react源碼中的生命周期和事件系統(tǒng)實例解析
引言
這一章我想跟大家探討的是React
的生命周期與事件系統(tǒng)。
jsx的編譯結(jié)果
因為前面也講到jsx
在v17
中的編譯結(jié)果,除了標(biāo)簽名,其他的掛在標(biāo)簽上的屬性(比如class
),事件
(比如click
事件),都是放在_jsxRuntime.jsx
函數(shù)的第二參數(shù)上。表現(xiàn)為key:value
的形式,這里我們就會產(chǎn)生幾個問題。
react
是怎么知道函數(shù)體(事件處理函數(shù))是什么的呢?react
又是在什么階段去處理這些事件的呢?
這里我們先賣個關(guān)子,我們先來看看一個完整的React
應(yīng)用的完整的生命周期是怎么樣的,我們都知道React
分為類組件
與函數(shù)組件
,兩種組件的部分生命周期函數(shù)發(fā)生了一些變化
,在這里我會分別對兩種組件的生命周期做講解。
React組件的生命周期
組件掛載的時候的執(zhí)行順序
因為在_jsxRuntime.jsx
編譯jsx
對象的時候,我們會去做處理defaultProps
和propType
靜態(tài)類型檢查。所以這也算是一個生命周期吧。Class
組件具有單獨的constructor
,在mount
階段會去執(zhí)行這個構(gòu)造函數(shù),我曾經(jīng)做了部分研究,這個constructor
是類組件獨有的,還是class
獨有的?后來發(fā)現(xiàn)這個constructor
是class
獨有的,怎么理解這句話呢?
- 在《重學(xué)ES6》這本書中提到:
ES6
中新增了類的概念,一個類必須要有constructor
方法,如果在類中沒有顯示定義,則一個空的constructor
方法會被默認添加。對于ReactClassComponent
來講需要constructor
的作用就是用來初始化state
和綁定事件,另外一點就是聲明了constructor
,就必須要調(diào)用super
,我們一般用來接收props
傳遞。假如你不寫constructor
,那就沒法用props
了,當(dāng)然了要在constructor
中使用props
,也必須用super
接收才行。 - 所以對于類組件來講的話,
constructor
也算是一個生命周期鉤子。
getDerivedStateFromProps
會在調(diào)用 render 方法之前調(diào)用,并且在初始掛載及后續(xù)更新時都會被調(diào)用。它應(yīng)返回一個對象來更新 state,如果返回 null
則不更新任何內(nèi)容。
render
被調(diào)用時,它會檢查 this.props
和 this.state
的變化并返回以下類型之一:
- React 元素。通常通過 JSX 創(chuàng)建。例如,
<div />
會被 React 渲染為 DOM 節(jié)點,<MyComponent />
會被 React 渲染為自定義組件,無論是<div />
還是<MyComponent />
均為 React 元素。 - 數(shù)組或 fragments。 使得 render 方法可以返回多個元素。
- Portals。可以渲染子節(jié)點到不同的 DOM 子樹中。
- 字符串或數(shù)值類型。它們在 DOM 中會被渲染為文本節(jié)點。
- 布爾類型或
null
。什么都不渲染。(主要用于支持返回test && <Child />
的模式,其中 test 為布爾類型。)
componentDidMount()
會在組件掛載后(插入 DOM 樹中)立即調(diào)用。依賴于 DOM 節(jié)點的初始化應(yīng)該放在這里。在這里適合去發(fā)送異步請求。
組件更新的時候的執(zhí)行順序
getDerivedStateFromProps
=> shouldComponentUpdate()
=> render()
=> getSnapshotBeforeUpdate()
=> componentDidUpdate()
- 其中
shouldComponentUpdate
也被稱作為性能優(yōu)化的一種鉤子,其作用在于比較兩次更新的state
或props
是否發(fā)生變化,決定是否更新當(dāng)前組件,比較的方式是淺比較
,以前講過這里不再復(fù)述。 - 而
getSnapshotBeforeUpdate
函數(shù)在最近一次渲染輸出(提交到DOM
節(jié)點)之前調(diào)用。它使得組件能在發(fā)生更改之前從DOM
中捕獲一些信息。此生命周期方法的任何返回值將作為參數(shù)傳遞給componentDidUpdate()
。 componentDidUpdate()
會在更新后會被立即調(diào)用。首次渲染不會執(zhí)行
此方法。
組件卸載的時候執(zhí)行順序
componentWillUnmount()
會在組件卸載及銷毀之前直接調(diào)用。在此方法中執(zhí)行必要的清理操作,例如,清除timer
,取消網(wǎng)絡(luò)請求等等。
組件在發(fā)生錯誤的時候執(zhí)行順序
getDerivedStateFromError
=> componentDidCatch
關(guān)于這兩個鉤子,同學(xué)們可自行移步官網(wǎng)。
當(dāng)然上面的只是ClassComponent
的生命周期執(zhí)行順序,而在新版本的React中已經(jīng)刪除掉了componentDidMount
、componentDidUpdate
、componentWillUnMount
,取而代之的是useEffect
、useLayoutEffect
。那究竟是誰代替了他們?nèi)齻€呢?這個問題我已經(jīng)在React源碼解析系列(八) -- 深入hooks的原理 中闡述過了,這里不再復(fù)述。
現(xiàn)在來回答第一個問題:react是怎么知道函數(shù)體是什么的呢? 這個問題其實問的非常好,babel
解析后的jsx
本身只會去關(guān)注{事件名:函數(shù)名},但是每一個事件都是需要被注冊、綁定的,然后通過事件觸發(fā),來執(zhí)行綁定函數(shù)的函數(shù)體。解釋這種問題還是得要去看一下源碼里面的具體實現(xiàn)。
listenToAllSupportedEvents
我們在React源碼解析系列(二) -- 初始化組件的創(chuàng)建更新流程中提到rootFiber
與FiberRoot
的創(chuàng)建,創(chuàng)建完畢之后我們就需要去創(chuàng)建事件,創(chuàng)建事件的入口函數(shù)為listenToAllSupportedEvents
。
// packages/react-dom/src/events/DOMPluginEventSystem.js export function listenToAllSupportedEvents(rootContainerElement: EventTarget) { if (enableEagerRootListeners) { // enableEagerRootListeners默認值為false // listeningMarker就是一個隨機數(shù)+字符串,作為唯一值 if (rootContainerElement[listeningMarker]) { ... return; } rootContainerElement[listeningMarker] = true; // 遍歷allNativeEvents的所有事件 allNativeEvents.forEach(domEventName => { // 如果不是委托事件,沒有冒泡階段 // nonDelegatedEvents全部媒體事件, if (!nonDelegatedEvents.has(domEventName)) { listenToNativeEvent( domEventName, false, ((rootContainerElement: any): Element), null, ); } // 有冒泡階段 listenToNativeEvent( domEventName, true, ((rootContainerElement: any): Element), null, ); }); } } //listeningMarker // 唯一標(biāo)識 const listeningMarker = '_reactListening' + Math.random() .toString(36) .slice(2);
我們在這里必須要關(guān)注一下allNativeEvents
是什么東西,allNativeEvents
在源碼里體現(xiàn)為一個存儲著事件名的Set
結(jié)構(gòu):
export const allNativeEvents: Set<DOMEventName> = new Set();
接下來看看listenToNativeEvent
究竟干了些什么。
listenToNativeEvent
export function listenToNativeEvent( domEventName: DOMEventName,// 事件名 isCapturePhaseListener: boolean, // 根據(jù)上個函數(shù),這里應(yīng)該是確定是是能夠冒泡的事件 rootContainerElement: EventTarget, targetElement: Element | null, eventSystemFlags?: EventSystemFlags = 0, // 事件標(biāo)記 ): void { let target = rootContainerElement; //如果是selectionchange事件,加到dom上 if ( domEventName === 'selectionchange' && (rootContainerElement: any).nodeType !== DOCUMENT_NODE ) { target = (rootContainerElement: any).ownerDocument; } if ( targetElement !== null && !isCapturePhaseListener && nonDelegatedEvents.has(domEventName) // 非冒泡事件 ) { ... //滾動事件不冒泡 if (domEventName !== 'scroll') { return; } eventSystemFlags |= IS_NON_DELEGATED; // is_non_delegated 不是委托事件 target = targetElement; } //獲取dom上綁定的事件名數(shù)組 Set[] || const listenerSet = getEventListenerSet(target); // 處理事件名為捕獲階段與冒泡階段 Set[click_bubble] const listenerSetKey = getListenerSetKey( domEventName, isCapturePhaseListener, ); // 把沒有打過的IS_CAPTURE_PHASE的符合條件的事件,打上標(biāo)簽 if (!listenerSet.has(listenerSetKey)) { if (isCapturePhaseListener) { // 打上捕獲的標(biāo)簽 eventSystemFlags |= IS_CAPTURE_PHASE; } // 往節(jié)點上添加事件綁定 addTrappedEventListener( target, domEventName, eventSystemFlags, isCapturePhaseListener, ); // 往listenerSet中添加事件名 listenerSet.add(listenerSetKey); } } //getEventListenerSet export function getEventListenerSet(node: EventTarget): Set<string> { let elementListenerSet = (node: any)[internalEventHandlersKey]; if (elementListenerSet === undefined) { // 創(chuàng)建一個Set來存放事件名 elementListenerSet = (node: any)[internalEventHandlersKey] = new Set(); } return elementListenerSet; } // getListenerSetKey export function getListenerSetKey( domEventName: DOMEventName, capture: boolean, ): string { // capture捕獲,bubble冒泡 return `${domEventName}__${capture ? 'capture' : 'bubble'}`; } // addTrappedEventListener function addTrappedEventListener( targetContainer: EventTarget, // 容器 domEventName: DOMEventName, // 事件名 eventSystemFlags: EventSystemFlags, //事件名標(biāo)識 isCapturePhaseListener: boolean, // 事件委托 isDeferredListenerForLegacyFBSupport?: boolean, ) { // 創(chuàng)建具有優(yōu)先級的事件監(jiān)聽函數(shù),返回值為function let listener = createEventListenerWrapperWithPriority( targetContainer, domEventName, eventSystemFlags, ); ... targetContainer = enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport ? (targetContainer: any).ownerDocument : targetContainer; let unsubscribeListener; ... // 區(qū)分捕獲、冒泡 通過node.addEventListener綁定事件到節(jié)點上 if (isCapturePhaseListener) { if (isPassiveListener !== undefined) { unsubscribeListener = addEventCaptureListenerWithPassiveFlag( targetContainer, domEventName, listener, isPassiveListener, ); } else { unsubscribeListener = addEventCaptureListener( targetContainer, domEventName, listener, ); } } else { if (isPassiveListener !== undefined) { unsubscribeListener = addEventBubbleListenerWithPassiveFlag( targetContainer, domEventName, listener, isPassiveListener, ); } else { unsubscribeListener = addEventBubbleListener( targetContainer, domEventName, listener, ); } } } // createEventListenerWrapperWithPriority export function createEventListenerWrapperWithPriority( targetContainer: EventTarget, // 容器 domEventName: DOMEventName, // 事件名 eventSystemFlags: EventSystemFlags, //標(biāo)識 ): Function { // 獲取事件Map里面已經(jīng)標(biāo)記好的優(yōu)先級 const eventPriority = getEventPriorityForPluginSystem(domEventName); let listenerWrapper; // 根據(jù)優(yōu)先級不同綁定不同的執(zhí)行函數(shù) 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, ); }
在這里我們關(guān)注一下獲取優(yōu)先級getEventPriorityForPluginSystem
這里,你會不會產(chǎn)生一個疑問,React
內(nèi)部事件我們知道React
本身一定會給優(yōu)先級的,但是非React
事件呢,比如原生事件
,他們的優(yōu)先級是怎么確定的呢?不要急,我們看一看就知道了。
getEventPriorityForPluginSystem
export function getEventPriorityForPluginSystem( domEventName: DOMEventName, ): EventPriority { // 通過事件名獲取優(yōu)先級 const priority = eventPriorities.get(domEventName); // ContinuousEvent為默認優(yōu)先級 return priority === undefined ? ContinuousEvent : priority; } //eventPriorities const eventPriorities = new Map();
eventPriorities
本身是一個Map結(jié)構(gòu),我們可以發(fā)現(xiàn)兩個地方進行了eventPriorities.set()
的操作。
// packages/react-dom/src/events/DOMEventProperties.js function setEventPriorities( eventTypes: Array<DOMEventName>, priority: EventPriority, ): void { for (let i = 0; i < eventTypes.length; i++) { // 往eventPriorities添加優(yōu)先級 eventPriorities.set(eventTypes[i], priority); } } //registerSimplePluginEventsAndSetTheirPriorities function registerSimplePluginEventsAndSetTheirPriorities( eventTypes: Array<DOMEventName | string>, priority: EventPriority, ): void { for (let i = 0; i < eventTypes.length; i += 2) { const topEvent = ((eventTypes[i]: any): DOMEventName); const event = ((eventTypes[i + 1]: any): string); const capitalizedEvent = event[0].toUpperCase() + event.slice(1); // 改變事件名 click => onClick const reactName = 'on' + capitalizedEvent; // 往eventPriorities添加優(yōu)先級 eventPriorities.set(topEvent, priority); topLevelEventsToReactNames.set(topEvent, reactName); // 注冊捕獲階段,冒泡階段的事件 registerTwoPhaseEvent(reactName, [topEvent]); } }
這就說明,在這兩個函數(shù)里面已經(jīng)做好了優(yōu)先級的處理,那我們可以去看一下在哪里調(diào)用的這兩個函數(shù),我們發(fā)現(xiàn)在函數(shù)registerSimpleEvents
中,執(zhí)行了這兩個函數(shù),往eventPriorities
里面添加優(yōu)先級。
// packages/react-dom/src/events/DOMEventProperties.js export function registerSimpleEvents() { // 處理離散事件優(yōu)先級 registerSimplePluginEventsAndSetTheirPriorities( discreteEventPairsForSimpleEventPlugin, DiscreteEvent, ); // 處理用戶阻塞事件優(yōu)先級 registerSimplePluginEventsAndSetTheirPriorities( userBlockingPairsForSimpleEventPlugin, UserBlockingEvent, ); // 處理默認事件優(yōu)先級 registerSimplePluginEventsAndSetTheirPriorities( continuousPairsForSimpleEventPlugin, ContinuousEvent, ); // 處理其他事件優(yōu)先級 setEventPriorities(otherDiscreteEvents, DiscreteEvent); }
上述代碼中可以看到有非常多的Plugin
,代碼如下:
const discreteEventPairsForSimpleEventPlugin = [ ('cancel': DOMEventName), 'cancel', ('click': DOMEventName), 'click', ('close': DOMEventName), 'close', ('contextmenu': DOMEventName), 'contextMenu', ('copy': DOMEventName), 'copy', ('cut': DOMEventName), 'cut', ('auxclick': DOMEventName), 'auxClick', ('dblclick': DOMEventName), 'doubleClick', // Careful! ('dragend': DOMEventName), 'dragEnd', ('dragstart': DOMEventName), 'dragStart', ('drop': DOMEventName), 'drop', ('focusin': DOMEventName), 'focus', // Careful! ('focusout': DOMEventName), 'blur', // Careful! ('input': DOMEventName), 'input', ('invalid': DOMEventName), 'invalid', ('keydown': DOMEventName), 'keyDown', ('keypress': DOMEventName), 'keyPress', ('keyup': DOMEventName), 'keyUp', ('mousedown': DOMEventName), 'mouseDown', ('mouseup': DOMEventName), 'mouseUp', ('paste': DOMEventName), 'paste', ('pause': DOMEventName), 'pause', ('play': DOMEventName), 'play', ('pointercancel': DOMEventName), 'pointerCancel', ('pointerdown': DOMEventName), 'pointerDown', ('pointerup': DOMEventName), 'pointerUp', ('ratechange': DOMEventName), 'rateChange', ('reset': DOMEventName), 'reset', ('seeked': DOMEventName), 'seeked', ('submit': DOMEventName), 'submit', ('touchcancel': DOMEventName), 'touchCancel', ('touchend': DOMEventName), 'touchEnd', ('touchstart': DOMEventName), 'touchStart', ('volumechange': DOMEventName), 'volumeChange', ]; const otherDiscreteEvents: Array<DOMEventName> = [ 'change', 'selectionchange', 'textInput', 'compositionstart', 'compositionend', 'compositionupdate', ]; const userBlockingPairsForSimpleEventPlugin: Array<string | DOMEventName> = [ ('drag': DOMEventName), 'drag', ('dragenter': DOMEventName), 'dragEnter', ('dragexit': DOMEventName), 'dragExit', ('dragleave': DOMEventName), 'dragLeave', ('dragover': DOMEventName), 'dragOver', ('mousemove': DOMEventName), 'mouseMove', ('mouseout': DOMEventName), 'mouseOut', ('mouseover': DOMEventName), 'mouseOver', ('pointermove': DOMEventName), 'pointerMove', ('pointerout': DOMEventName), 'pointerOut', ('pointerover': DOMEventName), 'pointerOver', ('scroll': DOMEventName), 'scroll', ('toggle': DOMEventName), 'toggle', ('touchmove': DOMEventName), 'touchMove', ('wheel': DOMEventName), 'wheel', ]; const continuousPairsForSimpleEventPlugin: Array<string | DOMEventName> = [ ('abort': DOMEventName), 'abort', (ANIMATION_END: DOMEventName), 'animationEnd', (ANIMATION_ITERATION: DOMEventName), 'animationIteration', (ANIMATION_START: DOMEventName), 'animationStart', ('canplay': DOMEventName), 'canPlay', ('canplaythrough': DOMEventName), 'canPlayThrough', ('durationchange': DOMEventName), 'durationChange', ('emptied': DOMEventName), 'emptied', ('encrypted': DOMEventName), 'encrypted', ('ended': DOMEventName), 'ended', ('error': DOMEventName), 'error', ('gotpointercapture': DOMEventName), 'gotPointerCapture', ('load': DOMEventName), 'load', ('loadeddata': DOMEventName), 'loadedData', ('loadedmetadata': DOMEventName), 'loadedMetadata', ('loadstart': DOMEventName), 'loadStart', ('lostpointercapture': DOMEventName), 'lostPointerCapture', ('playing': DOMEventName), 'playing', ('progress': DOMEventName), 'progress', ('seeking': DOMEventName), 'seeking', ('stalled': DOMEventName), 'stalled', ('suspend': DOMEventName), 'suspend', ('timeupdate': DOMEventName), 'timeUpdate', (TRANSITION_END: DOMEventName), 'transitionEnd', ('waiting': DOMEventName), 'waiting', ];
而在registerSimplePluginEventsAndSetTheirPriorities
函數(shù)里面,我們發(fā)現(xiàn)了注冊事件registerTwoPhaseEvent
,我們繼續(xù)去深究一下,究竟是怎么注冊的。
registerTwoPhaseEvent
export function registerTwoPhaseEvent( registrationName: string, // 注冊事件reactName dependencies: Array<DOMEventName>, // 依賴 ): void { registerDirectEvent(registrationName, dependencies); registerDirectEvent(registrationName + 'Capture', dependencies); }
registerDirectEvent
// Mapping from registration name to event name export const registrationNameDependencies = {}; export function registerDirectEvent( registrationName: string, //react事件名onClick dependencies: Array<DOMEventName>, // 依賴 ) { ... // 以react事件名為key,dependencies為value的map對象 registrationNameDependencies[registrationName] = dependencies; if (__DEV__) { ... } // 遍歷依賴,把每一項加入到allNativeEvents中去 for (let i = 0; i < dependencies.length; i++) { allNativeEvents.add(dependencies[i]); } }
前面說allNativeEvents
是一個存儲事件名的Set
,這里往里面添加事件名
,就完成了事件注冊
。還沒有完,上面說過了事件注冊,與事件綁定,但是用戶點擊的時候,應(yīng)該怎么去觸發(fā)呢?上面的代碼,在獲取了優(yōu)先級之后,每個事件會根據(jù)當(dāng)前優(yōu)先級生成一個listenerWrapper
,這個listenerWrapper
也就是對應(yīng)的事件觸發(fā)綁定的函數(shù)。dispatchDiscreteEvent
、dispatchUserBlockingUpdate
、dispatchEvent
三個函數(shù)都通過bind執(zhí)行,我們都知道bind綁定的函數(shù),會返回一個新函數(shù),并不會立即執(zhí)行。所以我們也必須看看他的入?yún)⑹鞘裁础?/p>
this
:null
argments
:domEventName
:事件名,eventSystemFlags
:事件類型標(biāo)記,targetContainer
:目標(biāo)容器。
dispatchEvent
因為不管是dispatchDiscreteEvent
、dispatchUserBlockingUpdate
最后都會去執(zhí)行dispatchEvent
,所以我們可以看看他的實現(xiàn)。
// packages/react-dom/src/events/ReactDOMEventListener.js export function dispatchEvent( domEventName: DOMEventName, // 事件名 eventSystemFlags: EventSystemFlags, // 事件類型標(biāo)記 targetContainer: EventTarget, // 目標(biāo)容器 nativeEvent: AnyNativeEvent, // native事件 ): void { ... // 如果被阻塞了,嘗試調(diào)度事件 并返回掛載的實例或者容器 const blockedOn = attemptToDispatchEvent( domEventName, eventSystemFlags, targetContainer, nativeEvent, ); if (blockedOn === null) { // We successfully dispatched this event. ... return; } ... // 調(diào)度事件,觸發(fā)事件 dispatchEventForPluginEventSystem( domEventName, eventSystemFlags, nativeEvent, null, targetContainer, ); } // dispatchEventForPluginEventSystem export function dispatchEventForPluginEventSystem( domEventName: DOMEventName, eventSystemFlags: EventSystemFlags, nativeEvent: AnyNativeEvent, targetInst: null | Fiber, targetContainer: EventTarget, ): void { ... //批量更新事件 batchedEventUpdates(() => dispatchEventsForPlugins( domEventName, eventSystemFlags, nativeEvent, ancestorInst, targetContainer, ), ); } // batchedEventUpdates export function batchedEventUpdates(fn, a, b) { ... isBatchingEventUpdates = true; try { // fn : ()=>dispatchEventsForPlugins //(domEventName,eventSystemFlags,ativeEvent,ancestorInst,targetContainer,), // a: undefined // b: undefined return batchedEventUpdatesImpl(fn, a, b); // batchedEventUpdatesImpl(fn, a, b) => // Defaults // let batchedUpdatesImpl = function(fn, bookkeeping) { // return fn(bookkeeping); 執(zhí)行dispatchEventsForPlugins }; } finally { ... } }
dispatchEventsForPlugins
function dispatchEventsForPlugins( domEventName: DOMEventName, eventSystemFlags: EventSystemFlags, nativeEvent: AnyNativeEvent, targetInst: null | Fiber, targetContainer: EventTarget, ): void { const nativeEventTarget = getEventTarget(nativeEvent); const dispatchQueue: DispatchQueue = []; //創(chuàng)建合成事件,遍歷fiber鏈表,將會觸發(fā)的事件加入到dispatchQueue中 extractEvents( dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer, ); //觸發(fā)時間隊列,執(zhí)行事件 processDispatchQueue(dispatchQueue, eventSystemFlags); } //extractEvents function extractEvents( dispatchQueue: DispatchQueue, domEventName: DOMEventName, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: null | EventTarget, eventSystemFlags: EventSystemFlags, targetContainer: EventTarget, ) { ... let from; let to; ... const leave = new SyntheticEventCtor( leaveEventType, eventTypePrefix + 'leave', from, nativeEvent, nativeEventTarget, ); leave.target = fromNode; leave.relatedTarget = toNode; let enter: KnownReactSyntheticEvent | null = null; ... accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to); } //accumulateEnterLeaveTwoPhaseListeners export function accumulateEnterLeaveTwoPhaseListeners( dispatchQueue: DispatchQueue, leaveEvent: KnownReactSyntheticEvent, enterEvent: null | KnownReactSyntheticEvent, from: Fiber | null, to: Fiber | null, ): void { const common = from && to ? getLowestCommonAncestor(from, to) : null; if (from !== null) { accumulateEnterLeaveListenersForEvent( dispatchQueue, leaveEvent, from, common, false, ); } if (to !== null && enterEvent !== null) { accumulateEnterLeaveListenersForEvent( dispatchQueue, enterEvent, to, common, true, ); } } // accumulateEnterLeaveListenersForEvent function accumulateEnterLeaveListenersForEvent( dispatchQueue: DispatchQueue, event: KnownReactSyntheticEvent, target: Fiber, common: Fiber | null, inCapturePhase: boolean, ): void { // 獲取注冊的事件名 const registrationName = event._reactName; // 事件處理函數(shù)容器 const listeners: Array<DispatchListener> = []; //節(jié)點實例 let instance = target; // 遍歷fiber,獲取fiber上的事件對應(yīng)的事件處理函數(shù) while (instance !== null) { if (instance === common) { break; } const {alternate, stateNode, tag} = instance; if (alternate !== null && alternate === common) { break; } if (tag === HostComponent && stateNode !== null) { const currentTarget = stateNode; // 根據(jù)捕獲階段,還是冒泡階段處理不同的函數(shù)邏輯 if (inCapturePhase) { const captureListener = getListener(instance, registrationName); if (captureListener != null) { // 加入到listeners中 // instance:當(dāng)前fiebr實例 // currentTarget:當(dāng)前dom listeners.unshift( createDispatchListener(instance, captureListener, currentTarget), ); } } else if (!inCapturePhase) { // 冒泡 const bubbleListener = getListener(instance, registrationName); if (bubbleListener != null) { // 加入到listeners中 listeners.push( createDispatchListener(instance, bubbleListener, currentTarget), ); } } } // 當(dāng)前fiber實例的父級 instance = instance.return; } if (listeners.length !== 0) { // 把事件、事件處理函數(shù)全部推到dispatchQueue中 dispatchQueue.push({event, listeners}); } } // processDispatchQueue export function processDispatchQueue( dispatchQueue: DispatchQueue, // 事件隊列 eventSystemFlags: EventSystemFlags, // 事件類型標(biāo)記 ): void { const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0; for (let i = 0; i < dispatchQueue.length; i++) { const {event, listeners} = dispatchQueue[i]; // 執(zhí)行事件,完成觸發(fā) processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling. } // This would be a good time to rethrow if any of the event handlers threw. rethrowCaughtError(); }
所以到這里,React
的事件系統(tǒng)就解析完了,在這里上面的問題就很好解答了,React
對事件名與事件處理函數(shù)對做了綁定,并在創(chuàng)建rootFiber
的時候就做了事件注冊
、事件綁定
、事件調(diào)度
。那么他們的執(zhí)行流程大致如下:
總結(jié)
這一章主要是介紹組件在mount
、update
、destroy
階段的生命周期執(zhí)行順序與React
事件系統(tǒng)的注冊,綁定,調(diào)度更新等
以上就是react源碼中的生命周期和事件系統(tǒng)實例解析的詳細內(nèi)容,更多關(guān)于react源碼生命周期事件系統(tǒng)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React class和function的區(qū)別小結(jié)
Class組件和Function組件是React中創(chuàng)建組件的兩種主要方式,本文主要介紹了React class和function的區(qū)別小結(jié),具有一定的參考價值,感興趣的可以了解一下2023-10-10使用webpack配置react-hot-loader熱加載局部更新
這篇文章主要介紹了使用webpack配置react-hot-loader熱加載局部更新,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01