As we know that Concurrent was enabled by default in React 18.
To implement this feature, there are new 2 hooks that come into the game: useDeferredValue
and useTransition
You might want to know how it work. Let’s find out
I. useDeferredValue
1. Mount phase:
In mount phase (first render), useDeferredValue will be treated as below :
function mountDeferredValue<T>(value: T): T {
const hook = mountWorkInProgressHook();
hook.memoizedState = value;
return value;
}
This mean it will save the current value into memoizedState and directly return it.
So on first render, deferredValue will be the same as input value.
2. Update phase:
When component have an update, useDeferredValue will be treated as below:
function updateDeferredValue<T>(value: T): T {
const hook = updateWorkInProgressHook();
const resolvedCurrentHook: Hook = (currentHook: any);
const prevValue: T = resolvedCurrentHook.memoizedState;
return updateDeferredValueImpl(hook, prevValue, value);
}
function updateDeferredValueImpl<T>(hook: Hook, prevValue: T, value: T): T {
const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
if (shouldDeferValue) {
if (!is(value, prevValue)) {
const deferredLane = claimNextTransitionLane();
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
deferredLane,
);
markSkippedUpdateLanes(deferredLane);
hook.baseState = true;
}
return prevValue;
} else {
if (hook.baseState) {
hook.baseState = false;
markWorkInProgressReceivedUpdate();
}
hook.memoizedState = value;
return value;
}
}
in updateDeferredValue, it will get the previos value that was already stored in memoizedState and put the previos value and current value as parameter to next function updateDeferredValueImpl
in updateDeferredValueImpl, there will be a check if the current lanes contain a UrgentLanes
const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
Dig into includesOnlyNonUrgentLanes we will know UrgentLane are :
SyncLane | InputContinuousLane | DefaultLane;
So we can see that if current process contain a UrgentLane it will skip update DeferredValue and return the previos value immediately after schedule a render with Transition Lane (not prioritized).
Otherwise if current process contain only nonUrgentLanes, it will memoize and update the new value on useDeferredValue.
The flow is simply as below diagram:
II. useTransition
1. Mount phase
function mountTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const [isPending, setPending] = mountState(false);
// The `start` method never changes.
const start = startTransition.bind(null, setPending);
const hook = mountWorkInProgressHook();
hook.memoizedState = start;
return [isPending, start];
}
Interesting, in mount phase, useTransition will use useState (mountState) to store its state "isPending
".
Next, it will generate start function by bind setPending
(dispatch setState) into startTransition
, then put this function into hook. And return to the component.
Let's see what's startTransition
do (I've removed all comments and warning code to make function shorter) :
function startTransition(setPending, callback) {
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(
higherEventPriority(previousPriority, ContinuousEventPriority),
);
setPending(true);
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
try {
setPending(false);
callback();
} finally {
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}
So at first, it will ensure the current priority is higher than ContinuousEventPriority
, then set isPending
to true
to mark as transition start.
After that, it will save current value of ReactCurrentBatchConfig.transition
into prevTransition
and then set it to 1.
Guess what, setting ReactCurrentBatchConfig.transition
to 1 will force put next updates into Transition Lane
.
const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
if (currentEventTransitionLane === NoLane) {
currentEventTransitionLane = claimNextTransitionLane();
}
return currentEventTransitionLane;
}
You can check it in requestUpdateLane
After process callback, it will reset ReactCurrentBatchConfig
into original by prevTransition
variable.
2. Update phase
Update phase for useTransition is kind of simpler, since trigger function start
was already memoized in hook.
function updateTransition(): [boolean, (() => void) => void] {
const [isPending] = updateState(false);
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
return [isPending, start];
}
Conclusion:
By looking at their code, we can see that useTransition and useDeferredValue will basically put the process into Transition Lane.
Also, Concurrent does not mean React doing multi-task in the same time. It just mean React
will process tasks with priority order. To do that, React has its Scheduler that may be discussed in another post.
Top comments (1)
Nice, that's what I was looking for. Thanks