认识
一、认识
useTransition
用于在 createRoot
开启的并发特性模式下, 开启并发渲染。主要解决浏览器执行大量渲染工作问题时的卡顿问题。useTransition
返回一个数组, 第一个元素为 isPending
, 表示当处于过渡状态的标志; 第二个是 startTransition
, 一个方法, 里面的更新任务都会被标记成过渡更新任务,过渡更新任务在渲染并发场景下,会被降级更新优先级,中断更新, 所以它不会让页面卡顿,React
让这些任务,在浏览器空闲时间执行。
1.1 startTransition vs 节流防抖
节流防抖 也可以解决大量渲染工作同时渲染卡顿的问题, 但是节流防抖导致更新任务的执行时间变长了, 视图改变的时间也会延长。所以 startTransition
与 节流防抖 的本质区别是: 一方面,节流防抖 本质上也是 setTimeout
,只不过控制了执行的频率,那么通过打印的内容就能发现,原理就是让 render
次数减少了。而 transitions
和它相比,并没有减少渲染的次数。另一方面,节流和防抖需要有效掌握 Delay Time
延时时间,如果时间过长,那么给人一种渲染滞后的感觉,如果时间过短,那么就类似于 setTimeout(fn,0)
还会造成前面的问题。而 startTransition
就不需要考虑这么多。
1.2 startTransition vs setTimeout
setTimeout
同样可以解决大量渲染工作同时渲染卡顿的问题, startTransition
相比 setTimeout
的优势和异同是: 一方面, startTransition
的处理逻辑和 setTimeout
有一个很重要的区别,setTimeout
是异步延时执行,而 startTransition
的回调函数是同步执行的。在 startTransition
之中任何更新,都会标记上 transition
,React
将在更新的时候,判断这个标记来决定是否完成此次更新。所以 Transition
可以理解成比 setTimeout
更早的更新。但是同时要保证 ui
的正常响应,在性能好的设备上,transition
两次更新的延迟会很小,但是在慢的设备上,延时会很大,但是不会影响 UI
的响应。另一方面,对于渲染并发的场景下,setTimeout
仍然会使页面卡顿。因为超时后,还会执行 setTimeout
的任务,它们与用户交互同样属于宏任务,所以仍然会阻止页面的交互。那么 transition
就不同了,在 conCurrent mode
下,startTransition
是可以中断渲染的 ,所以它不会让页面卡顿,React
让这些任务,在浏览器空闲时间执行
1.3 useTransition vs useDeferredValue
useDeferredValue
本质上和内部实现与 useTransition
一样都是标记成了过渡更新任务。但是, useTransition
是把 startTransition
内部的更新任务变成了过渡任务 transition
,而 useDeferredValue
是把原值通过过渡任务得到新的值,这个值作为延时状态。 一个是处理一段逻辑,另一个是生产一个新的状态。useDeferredValue
还有一个不同点就是这个任务,本质上在 useEffect
内部执行,而 useEffect
内部逻辑是异步执行的 ,所以它一定程度上更滞后于 useTransition
。 useDeferredValue = useEffect + transition
二、细节
2.1 hook
const hook = {
next: null,
baseQueue: null,
baseState: null,
updateQueue: null,
memoizedState: null
};
2.2 update
export const createUpdate = (
action,
lane,
hasEagerState = false,
eagerState = null
) => {
return {
lane,
action,
next: null,
hasEagerState,
eagerState
};
};
2.3 updateQueue
function createFCUpdateQueue() {
const updateQueue = createUpdateQueue();
updateQueue.lastEffect = null;
return updateQueue;
}
export const createUpdateQueue = () => {
return {
shared: {
pending: null
},
dispatch: null
};
};
updateQueue.shared.pending
: 存储 update
对象
updateQueue.dispatch
: 存储 dispatchAction.bind()
的值
2.4 hook.updateQueue
hook.updateQueue
用于存储 updateQueue
2.5 hook.memoizedState
hook.memoizedState
用于存储 state
值
2.6 fiber.memoizedState
fiber.memoizedState
用于存储 hooks
链表
三、执行函数组件
在 React.js
的 beginWork
阶段, 遇到 FunctionComponent
类型的 Fiber
, 会执行 updateFunctionComponent
。updateFunctionComponent
内部调用 renderWithHooks
。主要工作如下:
-
重置状态: 函数组件执行前, 重置当前正在处理的
hook
、当前正在使用的hook
-
记录当前组件
fiber
: 通过currentlyRenderingFiber
变量记录当前组件fiber
-
选择对应阶段的
Hook
:mount
阶段选择HooksDispatcherOnMount
作为Hooks Map
,update
阶段选择HooksDispatcherOnUpdate
作为Hooks Map
。 函数组件mount
阶段 创建hook
数据结构, 存储到fiber.memoizedState
或者hook.next
, 初次建立其hooks
与fiber
之间的关系。函数组件update
阶段 找到当前函数组件fiber
, 找到当前hook
, 更新hook
状态。 -
执行函数组件: 执行我们真正函数组件,所有的
hooks
将依次执行, 每个hook
内部要根据currentlyRenderingFiber
读取对应的内容, -
重置状态: 执行完
Component
, 立马重置currentlyRenderingFiber
, 防止函数组件外部调用。置当前正在处理的hook
、当前正在使用的hook
3.1 /packages/react-reconciler/fiberHooks.js
export function renderWithHooks(workInProgress, Component, lane) {
renderLane = lane;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
const current = workInProgress.alternate;
if (current !== null) {
// update
currentDispatcher.current = HooksDispatcherOnUpdate;
} else {
// mount
currentDispatcher.current = HooksDispatcherOnMount;
}
const props = workInProgress.pendingProps;
const children = Component(props);
currentHook = null;
renderLane = NoLane;
workInProgressHook = null;
currentlyRenderingFiber = null;
return children;
}
3.2 /packages/react-reconciler/fiberHooks.js
/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
use,
useRef: mountRef,
useMemo: mountMemo,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useCallback: mountCallback,
useTransition: mountTransition
};
/**
* @description: Update 阶段 Hooks 实现
*/
const HooksDispatcherOnUpdate = {
use,
useRef: updateRef,
useMemo: updateMemo,
useState: updateState,
useEffect: updateEffect,
useContext: readContext,
useCallback: updateCallback,
useTransition: updateTransition
};
四、函数组件 mount 阶段 Hook
useTransition
创建一个 Hook
, 通过调用 mountState
维护一个 isPending
状态, 定义一个 startTransition
方法, 并通过 .bind
将 setIsPending
方法传入, 将 startTransition.bind
方法保存到 hook.memoizedState
, 返回 isPending
和 startTransition
组成的数组即可。
function mountTransition() {
const [isPending, setPending] = mountState(false);
const hook = mountWorkInProgressHook();
const start = startTransition.bind(null, setPending);
hook.memoizedState = start;
return [isPending, start];
}
五、函数组件 update 阶段 Hook
useTransition
获取对应 Hook
, 通过调用 updateState
维护一个 isPending
状态, 从 hook.memoizedState
上获取到 startTransition
方法, 返回 isPending
和 startTransition
组成的数组即可。
function updateTransition() {
const [isPending] = updateState();
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
return [isPending, start];
}
六、执行回调函数, 并指定过渡任务优先级
startTransition
中, 会执行传入的 callback
。执行 callback
前, 会更新全局配置 currentBatchConfig.transition
为 true
, 表示当前任务为过度任务。当执行 callback
的过程中, 如果 callback
内部有更新任务。此时获取的任务优先级时, 会判断 currentBatchConfig.transition
, 如果为 true
, 那么此时的更新任务优先级就是 过渡任务优先级。callback
执行完毕后, 将全局配置 currentBatchConfig.transition
恢复到之前状态。
6.1 startTransition
function startTransition(setPending, callback) {
setPending(true);
const prevTransition = currentBatchConfig.transition;
currentBatchConfig.transition = 1;
callback();
setPending(false);
currentBatchConfig.transition = prevTransition;
}
6.2 requestUpdateLane
export function requestUpdateLane() {
const isTransition = ReactCurrentBatchConfig.transition !== null;
if (isTransition) {
return TransitionLane;
}
const currentSchedulerPriority = unstable_getCurrentPriorityLevel();
const lane = schedulerPriorityToLane(currentSchedulerPriority);
return lane;
}