useTransiton的实现
本系列是讲述从0开始实现一个react18的基本版本。通过实现一个基本版本,让大家深入了解React
内部机制。
由于React
源码通过Mono-repo 管理仓库,我们也是用pnpm
提供的workspaces
来管理我们的代码仓库,打包我们使用rollup
进行打包。
系列文章:
本章我们通过一个实际的api来讲解react的并发中的运用。在React18中,新增了一个useTransition
的api,我们先来看一下这个api的使用。
useTransition的使用
useTransition
主要的目的是让我们更新状态,但是不去堵塞UI的渲染。
const [isPending, startTransition] = useTransition()
它不接受任务参数,返回一个数组。包含ispending
告诉我们当前的任务的状态。startTransition
是一个函数,接受一个函数,用来更新状态,启动更新。
具体的例子和使用可以看官方文档。
useTransition的实现
接下来我们从原理和具体的代码分析useTransition
是如何实现的。也可以更加的让我们去了解react
是如何利用优先级去实现并发的。
useTransition
的原理
从之前的章节中,我们晓得react
内部通过优先级来区分任务的执行顺序。useTransition
通过低优先级的更新策略来实现不堵塞UI的渲染。
useTransition
的作用分为2个部分:
- 切换UI -> 触发更新
- 回调触发更新(低优先级) -> 先显示旧的UI -> 等待新的UI任务加载完成后,再显示新的UI
如下例子中,但我们点击调用selectTab
函数时,会触发startTransition
函数,传入一个回调函数。
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState("about");
const [number, setNumber] = useState("about");
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab)
setNumber(1)
});
}
整个流程类似大致如图:
- 当调用
selectTab
的时候,首先会触发startTransition
, 传入一个callback
- 在
startTransition
执行的时候,react
内部调用setPending(true)
。然后全局标识currentBatchConfig.transition = 1
进入transition
的优先级中 - 同步调用
callback()
, 在内部会产生2个任务,由于此时的优先级已经被标记为transition
,所以此时的任务也会是transition
优先级推入taskQueue
中等待调用。 - 同时设置
setPending(false)
,这个任务也是transition
优先级,等待调度 - 重置全局标识,恢复正常的优先级去调度。
useTransition
的代码部分 - mount和update
首先我们理解useTransition
是一个hook
。所以会分为2个mount
和update
2个部分。所有的hook
逻辑都在fiberHook
文件中。
初始化mountTransition
在初始化渲染阶段,当第一次执行useTransition
的时候,实际就是执行到mountTransition
, 需要返回1个数组。
包含isPending
以及startTransition
2个部分,用于过度动画和触发低优先级的更新。
function mountTransition(): [boolean, (callback: () => void) => void] {
const [isPending, setPending] = mountState(false);
const hook = mountWorkInProgressHook();
const start = startTransition.bind(null, setPending);
hook.memoizedState = start;
return [isPending, start];
}
从上面的代码中我们可以看出,内部调用了一个mountState
创建一个isPending
变量,用于标识执行的状态。
然后执行mountWorkInProgressHook
创建一个新的hook
结构。然后将start(startTransition)
绑定在memoizedState
上。以便于在update
的时候使用。
更新updateTransition
当我们点击的时候触发startTransition
。实际上是执行的逻辑是怎么样的呢?
我们来看看startTransition
的主要逻辑:
function startTransition(setPending: Dispatch<boolean>, callback: () => void) {
setPending(true); // 1. 触发一个高优先级的更新(同步)点击
const prevTransition = currentBatchConfig.transition;
currentBatchConfig.transition = 1;
callback(); // 2. 触发一个低优先级的更新
setPending(false);
currentBatchConfig.transition = prevTransition;
}
- 由于我们点击事件的优先级是
ImmediatePriority
, 所以setPending
的任务也是同步优先级执行。 currentBatchConfig.transition = 1
后,我们调用callback
, 这个时候会触发2个任务。- 每个任务都会去调用
requestUpdateLane
去绑定有一个优先级。所以我们在currentBatchConfig.transition = 1;
的阶段中,返回的都是TransitionLane
的优先级。
export function requestUpdateLane() {
// 增加transition 逻辑
const isTransition = ReactCurrentBatchConfig.transition !== null;
if (isTransition) {
return TransitionLane;
}
// 从调度器中获取优先级
const currentSchedulerPriority = unstable_getCurrentPriorityLevel();
// scheduler优先级 to lane
const lane = schedulerPriorityToLane(currentSchedulerPriority);
return lane;
}
- 所以此次执行完后,
callback
中的任务以及setPending(false)
都是TransitionLane
的优先级
当更新的时候执行到对应的组件的时候。useTransition
实际对应的updateTransition
。返回isPending
以及下次更新start
函数
function updateTransition(): [boolean, (callback: () => void) => void] {
const [isPending, _] = updateState();
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
return [isPending as boolean, start];
}
等待高优先级的任务执行完后。开始执行是TransitionLane
的优先级的任务,设置ispending = false
, 以及对应的callback
中的更新任务。
至此,我们的useTransition
hook的逻辑就完全执行完成。
转载自:https://juejin.cn/post/7214735181172047931