“This is the third day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021.”
React version: V17.0.3
1. Hook entrance
Remember that react-hooks Hooks are used in the same way that they are used in the same way. Remember that react-hooks Hooks are used in the same way that they are used in the same way.
-
Mount stage:
useMemo = ReactCurrentDispatcher.current.useMemo = HooksDispatcherOnMount.useMemo = mountMemo;
-
Update phase:
useMemo = ReactCurrentDispatcher.current.useMemo = HooksDispatcherOnUpdate.useMemo = updateMemo;
Therefore, the component performs useMemo in the mount phase, which is actually mountMemo, and performs updateMemo in the update phase.
2. Mount stage
Mount useMemo mountMemo mountMemo mountMemo mountMemo mountMemo
2.1 mountMemo
// packages/react-reconciler/src/ReactFiberHooks.new.js function mountMemo<T>( nextCreate: () = > T, the first parameter to the / / useMemo: callback deps: Array < mixed > | void | null, / / useMemo second parameter: the dependency Array: Const hook = mountWorkInProgressHook(); const hook = mountWorkInProgressHook(); Const nextDeps = deps === undefined? null : deps; NextCreate () const nextValue = nextCreate(); // Save the calculated value and corresponding dependencies to the memoizedState property of the hook object. MemoizedState = [nextValue, nextDeps]; // return nextValue; }Copy the code
A hook object will be added to the workInProgressHook unidirectionallist:
const hook = mountWorkInProgressHook();
Copy the code
We then initialize the dependency array that needs to be saved according to useMemo’s second argument, deps:
const nextDeps = deps === undefined ? null : deps;
Copy the code
UseMemo’s first argument, callback, is then executed to calculate the value to be cached:
const nextValue = nextCreate();
Copy the code
Finally, the calculated value and corresponding dependencies are saved to the memoizedState property of the Hook object and the cached value is returned:
// Save the calculated value and corresponding dependencies to the memoizedState property of the hook object. MemoizedState = [nextValue, nextDeps]; // return nextValue;Copy the code
As you can see, useMemo’s memoizedState stores not a specific value, but an array of cached values and dependencies.
2.2 mountWorkInProgressHook
In the mountMemo() function, a new hook object is created using the mountWorkInProgressHook() function. Let’s see how it is created:
/ / packages/react - the reconciler/SRC/ReactFiberHooks. New. Js / / create a new hook object, And return the current workInProgressHook object. Function mountWorkInProgressHook(): Hook {const Hook: Hook = {memoizedState: memoizedState: null, baseState: null, baseQueue: null, queue: null, next: null, }; // Hooks are stored as a linked list on the fiber's memoizedState field // New Hooks are stored as a linked list on the current Fiber MemoizedState property // Only when the page is opened for the first time, If (workInProgressHook === null) {// This is the first hook in the list CurrentlyRenderingFiber: The work-in-progress fiber. I've named it differently to distinguish it fromthe work-in-progress hook. currentlyRenderingFiber.memoizedState = workInProgressHook = hook; } else {// Append to the end of the list. WorkInProgressHook = workInProgressHook. Next = hook; } return workInProgressHook; }Copy the code
If the global workInProgressHook object does not exist (value null), the component will assign the new hook object to the workInProgressHook object when rendering the first time. Also assign the hook object to the memoizedState property of currentlyRenderingFiber. If workInProgressHook is not null, The hook object is connected to the tail of the workInProgressHook to form a one-way linked list.
3. Update phase
In the update phase, the component performs useMemo, which is actually updatemo. Let’s look at the implementation of updateMemo.
3.1 updateMemo
// packages/react-reconciler/src/ReactFiberHooks.new.js function updateMemo<T>( nextCreate: () = > T, the first parameter to the / / useMemo: callback deps: Array < mixed > | void | null, / / useMemo second parameter: the dependency Array: Const hook = updateWorkInProgressHook(); const hook = updateWorkInProgressHook(); Const nextDeps = deps === undefined? null : deps; [nextValue, nextDeps] const prevState = hook.memoizedState; if (prevState ! == null) { // Assume these are defined. If they're not, areHookInputsEqual will warn. if (nextDeps ! == null) { const prevDeps: Array<mixed> | null = prevState[1]; If (areHookInputsEqual(nextDeps, prevDeps)) {if (areHookInputsEqual(nextDeps, prevDeps)) { Return prevState[0]; NextValue = nextCreate();} nextValue = nextCreate();} nextCreate(); // Save the calculated new value and corresponding dependencies to the memoizedState property of the hook object. MemoizedState = [nextValue, nextDeps]; // // returns the calculated new value return nextValue; }Copy the code
Update (fiber) {update (fiber) {update (fiber) {
const hook = updateWorkInProgressHook();
Copy the code
We then get the new dependency so that we can compare it with the old dependency to determine if we need to re-execute the first parameter of useMemo to compute the new value:
const nextDeps = deps === undefined ? null : deps;
Copy the code
Then get the previous dependency array from the current hook object and call areHookInputsEqual() to compare whether the previous dependency array is the same:
[nextValue, nextDeps] const prevState = hook.memoizedState; if (prevState ! == null) { // Assume these are defined. If they're not, areHookInputsEqual will warn. if (nextDeps ! == null) { const prevDeps: Array<mixed> | null = prevState[1]; If (areHookInputsEqual(nextDeps, prevDeps)) {if (areHookInputsEqual(nextDeps, prevDeps)) { Return prevState[0]; }}}Copy the code
If areHookInputsEqual() returns true, the dependency array is the same, and you do not need to re-execute nextCreate to compute the new value. Instead, you directly return the last evaluated value.
If areHookInputsEqual() returns false, the following statement is executed:
Const nextValue = nextCreate(); const nextValue = nextCreate(); const nextValue = nextCreate(); // Save the calculated new value and corresponding dependencies to the memoizedState property of the hook object. MemoizedState = [nextValue, nextDeps]; // // returns the calculated new value return nextValue;Copy the code
UseMemo performs the above code to calculate the new value each time it renders, as long as the preceding and subsequent dependency arrays are different or no dependency arrays are provided. Therefore, when we use useMemo, we should pass in an array of dependencies to help avoid costly calculations on every render.
3.2 updateWorkInProgressHook
In the updateMemo() function, the workInProgressHook () function is used to retrieve the working Hook. Let’s look at the implementation of updateWork progresShook:
// packages/react-reconciler/src/ReactFiberHooks.new.js function updateWorkInProgressHook(): Hook { // This function is used both for updates and for re-renders triggered by a // render phase update. It assumes there is either a current hook we can // clone, or a work-in-progress hook from a previous render pass that we can // use as a base. When we reach the end of the base The list, we must switch to / / the dispatcher, informs for mounts. / / get the current under a hook hook nextCurrentHook: null | hook; if (currentHook === null) { const current = currentlyRenderingFiber.alternate; if (current ! == null) { nextCurrentHook = current.memoizedState; } else { nextCurrentHook = null; } } else { nextCurrentHook = currentHook.next; } / / to remove a hook for the current hook let nextWorkInProgressHook: null | hook; if (workInProgressHook === null) { nextWorkInProgressHook = currentlyRenderingFiber.memoizedState; } else { nextWorkInProgressHook = workInProgressHook.next; } if (nextWorkInProgressHook ! == null) { // There's already a work-in-progress. Reuse it. workInProgressHook = nextWorkInProgressHook; nextWorkInProgressHook = workInProgressHook.next; currentHook = nextCurrentHook; } else {// Clone from the current hook. // Copy the current hook as workInProgressHook invariant(nextCurrentHook! == null, 'Rendered more hooks than during the previous render.', ); currentHook = nextCurrentHook; const newHook: Hook = { memoizedState: currentHook.memoizedState, baseState: currentHook.baseState, baseQueue: currentHook.baseQueue, queue: currentHook.queue, next: null, }; if (workInProgressHook === null) { // This is the first hook in the list. currentlyRenderingFiber.memoizedState = workInProgressHook = newHook; } else { // Append to the end of the list. workInProgressHook = workInProgressHook.next = newHook; } } return workInProgressHook; }Copy the code
There are two cases:
- If it is in the Render phase, the next hook is taken as the current hook and the workInProgressHook is returned;
- If it is in the Re-render phase, the current workInProgressHook will continue to be updated in the current processing cycle, and finally return the workInProgressHook.
AreHookInputsEqual () is the same as areHookInputsEqual().
3.3 areHookInputsEqual
// packages/react-reconciler/src/ReactFiberHooks.new.js function areHookInputsEqual( nextDeps: Array<mixed>, prevDeps: Array < mixed > | null) {/ / deleted if dev code (prevDeps = = = null) {/ / delete the dev code return false. For (let I = 0; let I = 0; i < prevDeps.length && i < nextDeps.length; If (is(nextDeps[I], prevDeps[I])) {continue; } return false; } return true; }Copy the code
The areHookInputsEqual() function iterates through the DEps, calling is to compare each dependency in the dependency array. Note that is is a shallow comparison, meaning that deep comparisons must be updated. Here is the is method source code:
// packages/shared/objectIs.js function is(x: any, y: any) { return ( (x === y && (x ! == 0 || 1 / x === 1 / y)) || (x ! == x && y ! == y) // eslint-disable-line no-self-compare ); } const objectIs: (x: any, y: any) => boolean = typeof Object.is === 'function' ? Object.is : is; export default objectIs;Copy the code
The object.is () method checks whether two values are the same. If the current browser supports this method, call this method to check whether the two dependencies are the same. If not, call the React implementation is method to compare them.
4. UseMemo flowchart
5, summary
We know that useMemo will return the calculated Memoized value, and it will only recalculate the Memoized value if a dependency changes. This optimization helps avoid costly calculations every time you render. Although useMemo can cache data, it should not be abused. We should consider which data is worth caching, because useMemo takes time not only to calculate values, but also to compare dependencies. We should measure this so that we can better use hook. Not optimization for optimization’s sake.