preface

The React source reference version is 17.0.3. This is the React source series second, it is recommended that first look at the source of the students from the first to see, so more coherent, there are source series links below.

The warm-up to prepare

I’m not going to talk about useLayoutEffect here, it’s the same code as useEffect, the difference is the timing of execution. Specific can refer to the React source series two: React rendering mechanism.

Initialize the mount

mountEffect

< span style = “font-size: 14px; font-size: 14px;”

var hook = mountWorkInProgressHook();
Copy the code

In the mountEffect method, there are only a few lines of code. Let’s read a few parameters first:

  • FiberFlags: Update flags with side effects to mark where hooks are locatedfiber;
  • HookFlags: side effect flags;
  • Create: the callback function passed by the consumer;
  • Deps: Array dependencies passed in by the consumer;
Function mountEffectImpl(fiberFlags, hookFlags, create, deps) {var hook = mountWorkinProgreshookshook (); Var nextDeps = deps === undefined? Var nextDeps = deps === undefined? null : deps; / / to hook the fiber have side effects on updated tags currentlyRenderingFiber $1. Flags | = fiberFlags; / / will be side effects deposit to fiber. The operation memoizedState. J hook. The hook. In memoizedState memoizedState = pushEffect (HasEffect | hookFlags, create, undefined, nextDeps); }Copy the code

How do you store side effect updates in React, mainly pushEffect

Function pushEffect(tag, create, destroy, deps) {var effect = {tag: tag, create: Create, // callback destroy: destroy, // return (mount) deps: deps, // rely on array // next: null}; // The following chunk of code looks complicated, but does it sound familiar? var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue; if (componentUpdateQueue === null) { componentUpdateQueue = createFunctionComponentUpdateQueue(); currentlyRenderingFiber$1.updateQueue = componentUpdateQueue; / / effect. Next = effect form circular chain table componentUpdateQueue. LastEffect = effect. The next = effect; } else { var lastEffect = componentUpdateQueue.lastEffect; if (lastEffect === null) { componentUpdateQueue.lastEffect = effect.next = effect; } else { var firstEffect = lastEffect.next; lastEffect.next = effect; effect.next = firstEffect; componentUpdateQueue.lastEffect = effect; } } return effect; }Copy the code

Next points to fisrtEffect, and the current pointer to the list points to the most recently added effect.

UseEffect initialization is as simple as that. To hook the fiber side effects on updated tags, and fiber. MemoizedState. J hook. MemoizedState and fiber updateQueue store the related side effects, these side effects through closed-loop linked list storage structure.

Update the update

updateEffect

UpdateWorkInProgressHook was covered in the previous article, but the main function is to create a newHook with a callback function to override the previous hook.

function updateEffectImpl(fiberFlags, hookFlags, create, deps) { var hook = updateWorkInProgressHook(); var nextDeps = deps === undefined ? null : deps; var destroy = undefined; if (currentHook ! == null) { var prevEffect = currentHook.memoizedState; destroy = prevEffect.destroy; if (nextDeps ! == null) { var prevDeps = prevEffect.deps; If (areHookInputsEqual(nextDeps, prevDeps)) {pushEffect(hookFlags, create, destroy,); nextDeps); return; }}} before / / and initialize as currentlyRenderingFiber $1. The flags | = fiberFlags; hook.memoizedState = pushEffect(HasEffect | hookFlags, create, destroy, nextDeps); }Copy the code

I’m sure the eagle-eyed reader has noticed that there are two pushEffect in the above code, one not assigned to hook.memoizedstate and one assigned, what’s the difference between the two?

With that in mind, let’s take a look at what this line of code does, because it creates two pushEffect.

if (areHookInputsEqual(nextDeps, prevDeps)){... }

Function areHookInputsEqual(nextDeps, prevDeps) {return false if (prevDeps === null); } // return false for (var I = 0; i < prevDeps.length && i < nextDeps.length; i++) { if (objectIs(nextDeps[i], prevDeps[i])) { continue; } return false; } // deps = [], or deps does not change; return true; }Copy the code

It determines whether the values in the dependent array have changed and whether deps is an empty array to return true and false. Returning true indicates that the callback function does not need to be called this time.

Now we understand the similarities and differences between the two pusheffects. If the internal pushEffect is a callback function that does not need to be called, and the external pushEffect is called. Take a closer look at these two lines of code:

PushEffect (hookFlags, create, destroy, nextDeps); // If inside, the first argument is hookFlags = 4 pushEffect(hookFlags, create, destroy, nextDeps); // if external, The first parameter is the HasEffect | hookFlags = 5 hooks. MemoizedState = pushEffect (HasEffect | hookFlags, create, destroy, nextDeps);Copy the code

The difference between these two lines of code is that the first argument passed in is the value of effect.tag. Effect.tag = 4 is not added to the side effect execution queue, whereas effect.tag = 5 is. Effects that are not added to the side effect execution queue are not executed. In this way, useEffect is cleverly implemented to determine whether the callback function needs to be executed based on deps.

Now, we’ve figured out that whether or not the deps in useEffect changes will create an effect for the callback and add it to the effect list and fiber.updatequeue, React uses the effect.tag to determine whether an effect should be added to the side effect queue.

Executive side effect

We now know that useEffect is executed asynchronously. So when will this callback side effect be executed? The useEffect callback function is executed after the layout phase. Now let’s look at the process of specific invocation execution.

I have drawn a simple flow chart that Outlines the call process. First, in the pre-mutation phase, tasks are created based on side effects and placed in the taskQueue, and requestHostCallback is executed. This method is involved in asynchronism, which first considers using MessageChannel to implement asynchronism. The next thing to consider is using setTimeout. When using MessageChannel, requestHostCallback immediately executes port.postMessage(null); The workLoop traverses the taskQueue and executes the task. In the case of a useEffect effect task, the workLoop calls flusnPassiveEffects.

Q: One might wonder why MessageChannel is preferred?

A: First of all, we need to understand that the purpose of React scheduling update is for time sharding, which means that the main thread is returned to the browser every once in A while to avoid page lag caused by occupying the main thread for A long time. The purpose of using MessageChannel and SetTimeout is to create a macro task that will not be executed until the browser main thread is idle after all the current microtasks have been executed. The reason for not giving priority to setTimeout is that the execution time of setTimeout is not accurate, which will cause a waste of time. Even setTimeout(fn, 0) can be understood by yourself, which will not be described in this article.

In schedulePassiveEffects, the effect list is determined based on the effect.tag on each effect:

function schedulePassiveEffects(finishedWork) { var updateQueue = finishedWork.updateQueue; var lastEffect = updateQueue ! == null ? updateQueue.lastEffect : null; if (lastEffect ! == null) { var firstEffect = lastEffect.next; var effect = firstEffect; Do {var _effect = effect, next = _effect.next, tag = _effect.tag; If ((tag & Passive$1)! == NoFlags$1 && (tag & HasEffect) ! == NoFlags$1) { enqueuePendingPassiveHookEffectUnmount(finishedWork, effect); enqueuePendingPassiveHookEffectMount(finishedWork, effect); } effect = next; } while (effect ! == firstEffect); }}Copy the code

In flushPassiveEffects, the destructor of the last update action is executed, followed by the callback of the current update action, and the return of the callback is used as the destructor of the next update action.

The function flushPassiveEffectsImpl () {/ / destruction of the last updated action function var unmountEffects = pendingPassiveHookEffectsUnmount; pendingPassiveHookEffectsUnmount = []; for (var i = 0; i < unmountEffects.length; i += 2) { ... Destroy ()} / / do the update callback function var mountEffects = pendingPassiveHookEffectsMount; pendingPassiveHookEffectsMount = []; for (var _i = 0; _i < mountEffects.length; _i += 2) { ... create() } }Copy the code

These two lines in the above code are from the side effect execution queue. We have filtered out the effects that do not need to be performed, and only the side effect functions on the queue are executed

var unmountEffects = pendingPassiveHookEffectsUnmount;

var mountEffects = pendingPassiveHookEffectsMount;
Copy the code

conclusion

After reading this article, we can understand the following questions:

  1. useEffectanduseLayoutEffectThe difference between?
  2. useEffectHow to determine if a callback function needs to be executed?
  3. useEffectIs it synchronous or asynchronous?
  4. useEffectHow is asynchrony implemented?
  5. useEffectWhy do you want to choose firstMessageChannelAsynchronous?

Article series arrangement:

  1. React Fiber;
  2. React source series 2: React rendering mechanism;
  3. React source series 3: hooks useState, useReducer;
  4. React code series 4: hooks useEffect;
  5. React code Series 5: Hooks useCallback, useMemo;
  6. React source Series 6: Hooks useContext;
  7. React source series 7: React synthesis events;
  8. React source series eight: React diff algorithm;
  9. React source series 9: React update mechanism;
  10. React source series 10: Concurrent Mode;

Reference:

React official documents;

Making;