Life cycle and event system in react source code

What I want to discuss with you in this chapter is yes Reactand no .生命周期事件系统

The compilation result of jsx

Because I also mentioned jsxthe v17compilation results in the above, except 标签名for other tags 属性(such as class) and 事件(such as clickevents), they are all placed on _jsxRuntime.jsxthe second parameter of the function. In the form of performance key:value, here we will have several problems.

  • reactHow do you know what the function body (event handler) is?
  • reactAt what stage are these events handled?

Let’s make a point here first. Let’s first take a look at what Reactthe complete life cycle of a complete application looks like. We all know that it Reactis divided into 类组件and 函数组件part of the life cycle functions of the two components. 发生了一些变化Here I will analyze the two components separately. Explain the life cycle of components.

React component life cycle

Execution order when components are mounted

Because when _jsxRuntime.jsxcompiling jsxobjects, we will do processing defaultPropsand propTypestatic type checking. So this is also a life cycle. ClassComponents are separate, and this constructor will be executed constructorin stages. I have done some research. Is this unique to class components, or is it unique? Later, I found out that this is unique, how to understand this sentence?mountconstructorclassconstructorclass

  • It is mentioned in the book "Re-learning ES6": ES6The concept of a class is added in , a class must have a constructormethod, if there is no explicit definition in the class, an empty constructormethod will be added by default. For ReactClassComponentspeaking constructor, the required function is to initialize stateand bind events. The other point is that constructorit must be called after it is declared super. We generally use it to receive and propspass. If you don't write constructorit, it won't work props. Of course, if you want constructorto use it in props, you must use superreception.
  • So for class components, it constructorcan be regarded as a life cycle hook.

getDerivedStateFromPropsWill be called before the render method is called, and will be called on the initial mount and subsequent updates. It should return an object to update state, if it nullreturns nothing will be updated.

renderWhen called, it checks this.propsand this.statefor changes and returns one of the following types:

  • React elements . Usually created via JSX. For example, <div />it will be rendered as a DOM node <MyComponent />by React, and it will be rendered as a custom component by React, whether it is <div />or <MyComponent />is a React element.
  • array or fragments . Enables the render method to return multiple elements.
  • Portals . It is possible to render child nodes into different DOM subtrees.
  • String or numeric type . They are rendered as text nodes in the DOM.
  • Boolean ornull . Nothing is rendered. (Mainly used to support patterns that test && <Child />return , where test is a boolean.)

componentDidMount()Will be called immediately after the component is mounted (inserted into the DOM tree). Initialization that depends on DOM nodes should go here. This is suitable for sending asynchronous requests.

Execution order when components are updated

getDerivedStateFromProps => shouldComponentUpdate() => render() => getSnapshotBeforeUpdate() => componentDidUpdate()

  • It shouldComponentUpdateis also called a hook for performance optimization. Its function is to compare the two updates stateor propswhether they have changed, and decide whether to update the current component. The comparison method is 浅比较that it has been mentioned before and will not be repeated here.
  • And the getSnapshotBeforeUpdatefunction is called before the most recent rendered output (submitted to the DOMnode). It enables components to capture some information DOMfrom them . Any return value from this lifecycle method will be passed as an argument to componentDidUpdate().
  • componentDidUpdate()will be called immediately after the update. 不会执行This method is first rendered .

Execution order when components are uninstalled

componentWillUnmount()卸载Will be called directly before the component 销毁. Perform necessary cleanup operations in this method, such as cleanup timer, 取消网络请求etc.

Component execution order when an error occurs

getDerivedStateFromError=> componentDidCatchRegarding these two hooks, students can move to the official website by themselves.

Of course, the above is just ClassComponentthe execution order of the life cycle, and in the new version of React componentDidMount, componentDidUpdate, , , have been deleted componentWillUnMountand replaced by useEffect, useLayoutEffect. So who exactly replaced the three of them? I have already explained this problem in the React source code analysis series (8) - the principle of in-depth hooks, so I won't repeat it here.

Now to answer the first question: how does react know what the body of the function is? This question is actually a very good one. babelAfter parsing, you jsxwill only pay attention to {事件名:函数名}it, but each event needs to be registered and bound, and then triggered by the event to execute the function body of the bound function. To explain this kind of problem, we still have to look at the specific implementation in the source code.

listenToAllSupportedEvents

We mentioned in the React source code analysis series (2) - the creation and update process of the initialization component. rootFiberAfter FiberRootthe creation is completed, we need to create an event. The entry function for creating an event is listenToAllSupportedEvents.

// packages/react-dom/src/events/DOMPluginEventSystem.js
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {if (enableEagerRootListeners) { // enableEagerRootListeners默认值为false// listeningMarker就是一个随机数+字符串,作为唯一值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
// 唯一标识
const listeningMarker ='_reactListening' +Math.random().toString(36).slice(2); 

We must pay attention to what allNativeEventsis here, allNativeEventswhich is reflected in the source code as a Setstructure storing event names:

export const allNativeEvents: Set<DOMEventName> = new Set(); 

Let's see listenToNativeEventwhat happened next.

listenToNativeEvent

export function listenToNativeEvent( domEventName: DOMEventName,// 事件名isCapturePhaseListener: boolean, // 根据上个函数,这里应该是确定是是能够冒泡的事件rootContainerElement: EventTarget,targetElement: Element | null,eventSystemFlags?: EventSystemFlags = 0, // 事件标记 ): 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上绑定的事件名数组 Set[] || const listenerSet = getEventListenerSet(target);// 处理事件名为捕获阶段与冒泡阶段 Set[click_bubble]const listenerSetKey = getListenerSetKey(domEventName,isCapturePhaseListener,);// 把没有打过的IS_CAPTURE_PHASE的符合条件的事件,打上标签if (!listenerSet.has(listenerSetKey)) {if (isCapturePhaseListener) {// 打上捕获的标签eventSystemFlags |= IS_CAPTURE_PHASE;}// 往节点上添加事件绑定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) {// 创建一个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, //事件名标识isCapturePhaseListener: boolean, // 事件委托isDeferredListenerForLegacyFBSupport?: boolean, ) {// 创建具有优先级的事件监听函数,返回值为functionlet listener = createEventListenerWrapperWithPriority(targetContainer,domEventName,eventSystemFlags,);...targetContainer =enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport? (targetContainer: any).ownerDocument: targetContainer;let unsubscribeListener;...// 区分捕获、冒泡 通过node.addEventListener绑定事件到节点上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, //标识 ): Function {// 获取事件Map里面已经标记好的优先级const eventPriority = getEventPriorityForPluginSystem(domEventName);let listenerWrapper;// 根据优先级不同绑定不同的执行函数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,);
} 

Here we focus on getting the priority getEventPriorityForPluginSystemhere. Will you have a question React? We know that internal events Reactwill definitely be given priority, but what about non- Reactevents, for example 原生事件, how are their priorities determined? Don't worry, we'll see when we take a look.

getEventPriorityForPluginSystem

export function getEventPriorityForPluginSystem( domEventName: DOMEventName, ): EventPriority {// 通过事件名获取优先级const priority = eventPriorities.get(domEventName);// ContinuousEvent为默认优先级 return priority === undefined ? ContinuousEvent : priority;
}

//eventPriorities
const eventPriorities = new Map(); 

eventPrioritiesIt is a Map structure itself, and we can find the operations performed in two places 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添加优先级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 => onClickconst reactName = 'on' + capitalizedEvent;// 往eventPriorities添加优先级eventPriorities.set(topEvent, priority);topLevelEventsToReactNames.set(topEvent, reactName);// 注册捕获阶段,冒泡阶段的事件registerTwoPhaseEvent(reactName, [topEvent]);}
} 

This means that the priority processing has been done in these two functions, so we can look at where these two functions are called. We found that in the function registerSimpleEvents, these two functions were eventPrioritiesexecuted added to it priority.

// packages/react-dom/src/events/DOMEventProperties.js
export function registerSimpleEvents() {// 处理离散事件优先级registerSimplePluginEventsAndSetTheirPriorities(discreteEventPairsForSimpleEventPlugin,DiscreteEvent,);// 处理用户阻塞事件优先级registerSimplePluginEventsAndSetTheirPriorities(userBlockingPairsForSimpleEventPlugin,UserBlockingEvent,);// 处理默认事件优先级registerSimplePluginEventsAndSetTheirPriorities(continuousPairsForSimpleEventPlugin,ContinuousEvent,);// 处理其他事件优先级setEventPriorities(otherDiscreteEvents, DiscreteEvent);
} 

You can see that there are many in the above code , the Plugincode is as follows:

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',
]; 

In the registerSimplePluginEventsAndSetTheirPrioritiesfunction, we found the registration event registerTwoPhaseEvent, let's go on to find out how it is registered.

registerTwoPhaseEvent

export function registerTwoPhaseEvent( registrationName: string, // 注册事件reactNamedependencies: 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事件名onClickdependencies: 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]);}
} 

It is said that it allNativeEventsis a storage event name Set, add it here 事件名, and you are done 事件注册. It’s not over yet. It’s mentioned above that event registration is bound to the event, but when the user clicks, how should it be triggered? In the above code, after obtaining the priority, each event will generate one according to the current priority listenerWrapper, which listenerWrapperis the corresponding event trigger binding function. dispatchDiscreteEvent, dispatchUserBlockingUpdate, and the dispatchEventthree functions are all executed through bind. We all know that the function bound by bind will return a new function and will not be executed immediately. So we also have to see what his entry is.

  • this:null
  • argments: domEventName: event name, eventSystemFlags: event type tag, targetContainer: target container.

dispatchEvent

Because no matter it is dispatchDiscreteEvent, dispatchUserBlockingUpdateit will be executed in the end dispatchEvent, so we can take a look at his implementation.

// packages/react-dom/src/events/ReactDOMEventListener.js
export function dispatchEvent( domEventName: DOMEventName, // 事件名eventSystemFlags: EventSystemFlags, // 事件类型标记targetContainer: EventTarget, // 目标容器nativeEvent: AnyNativeEvent, // native事件 ): void {...// 如果被阻塞了,尝试调度事件 并返回挂载的实例或者容器const blockedOn = attemptToDispatchEvent(domEventName,eventSystemFlags,targetContainer,nativeEvent,);if (blockedOn === null) {// We successfully dispatched this event....return;}...// 调度事件,触发事件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: undefinedreturn batchedEventUpdatesImpl(fn, a, b); // batchedEventUpdatesImpl(fn, a, b) =>// Defaults// let batchedUpdatesImpl = function(fn, bookkeeping) {// return fn(bookkeeping); 执行dispatchEventsForPlugins
};} finally {...}
} 

dispatchEventsForPlugins

function dispatchEventsForPlugins( domEventName: DOMEventName,eventSystemFlags: EventSystemFlags,nativeEvent: AnyNativeEvent,targetInst: null | Fiber,targetContainer: EventTarget, ): void {const nativeEventTarget = getEventTarget(nativeEvent);const dispatchQueue: DispatchQueue = [];//创建合成事件,遍历fiber链表,将会触发的事件加入到dispatchQueue中extractEvents(dispatchQueue,domEventName,targetInst,nativeEvent,nativeEventTarget,eventSystemFlags,targetContainer,);//触发时间队列,执行事件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;// 事件处理函数容器const listeners: Array<DispatchListener> = [];//节点实例let instance = target;// 遍历fiber,获取fiber上的事件对应的事件处理函数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;// 根据捕获阶段,还是冒泡阶段处理不同的函数逻辑if (inCapturePhase) {const captureListener = getListener(instance, registrationName);if (captureListener != null) {// 加入到listeners中// instance:当前fiebr实例// currentTarget:当前domlisteners.unshift(createDispatchListener(instance, captureListener, currentTarget),);}} else if (!inCapturePhase) {// 冒泡const bubbleListener = getListener(instance, registrationName);if (bubbleListener != null) {// 加入到listeners中listeners.push(createDispatchListener(instance, bubbleListener, currentTarget),);}}}// 当前fiber实例的父级instance = instance.return;}if (listeners.length !== 0) {// 把事件、事件处理函数全部推到dispatchQueue中dispatchQueue.push({event, listeners});}
}
// processDispatchQueue
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);//event system doesn't use pooling.}// This would be a good time to rethrow if any of the event handlers threw.rethrowCaughtError();
} 

So at this point, Reactthe analysis of the event system is over, and the above questions can be easily answered here. ReactThe event name and the event processing function pair are bound, and 创建rootFiberwhen it is done 事件注册, 事件绑定, 事件调度. Then their execution process is roughly as follows:

Summarize

This chapter mainly introduces the life cycle execution sequence of components in mount, update, and phases and the registration, binding, scheduling update, etc. of the event system.destroyReact

At last

Organized a set of "Interview Collection of Front-end Manufacturers", including HTML, CSS, JavaScript, HTTP, TCP protocol, browser, VUE, React, data structure and algorithm, a total of 201 interview questions, and made an answer for each question Answer and analyze.

Friends in need, you can click the card at the end of the article to receive this document and share it for free

Part of the documentation shows:



The length of the article is limited, and the following content will not be displayed one by one

Friends in need, you can click the card below to get it for free

Guess you like

Origin blog.csdn.net/web22050702/article/details/128653976