从框架作者角度聊:React调度算法的迭代过程
作者:卡颂
简介:《React技术揭秘》作者
来源:SegmentFault 思否社区
大家好,我卡颂。
React内部最难理解的地方就是调度算法,不仅抽象、复杂,还重构了一次。
可以说,只有React团队自己才能完全理解这套算法。
既然这样,那本文尝试从React团队成员的视角出发,来聊聊调度算法。
什么是调度算法
React在v16之前面对的主要性能问题是:当组件树很庞大时,更新状态可能造成页面卡顿,根本原因在于:更新流程是同步、不可中断的。
为了解决这个问题,React提出Fiber架构,意在将更新流程变为异步、可中断的。
最终实现的交互流程如下:
不同交互产生不同优先级的更新(比如onClick回调中的更新优先级最高,useEffect回调中触发的更新优先级一般)
调度算法从众多更新中选出一个优先级作为本次render的优先级
以步骤2选择的优先级对组件树进行render
expirationTime调度算法
// MAX_SIGNED_31_BIT_INT为最大31 bit Interger
update.expirationTime = MAX_SIGNED_31_BIT_INT - (currentTime + updatePriority);
例如,高优先级更新u1、低优先级更新u2的updatePriority分别为0、200,则
MAX_SIGNED_31_BIT_INT - (currentTime + 0) > MAX_SIGNED_31_BIT_INT - (currentTime + 200)
// 即
u1.expirationTime > u2.expirationTime;
如何表示“批次”
// 定义状态num
const [num, updateNum] = useState(0);
// ...某些修改num的地方
// 修改的方式1
updateNum(3);
// 修改的方式2
updateNum(num => num + 1);
第一种方式,不需考虑更新前的状态,直接将状态num修改为3 第二种方式,需要基于更新前的状态计算新状态
const isUpdateIncludedInBatch = priorityOfUpdate >= priorityOfBatch;
experationTime算法保证了render 异步可中断且永远是最高优先级的更新先被处理。
这一时期该特性被称为Async Mode。
IO密集型场景
组件树逻辑复杂导致更新时卡顿(因为组件render变为可中断) 重要的交互更快响应(因为不同交互产生更新的优先级不同)
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const t = setInterval(() => {
setCount(count => count + 1);
}, 1000);
return () => clearInterval(t);
}, []);
return (
<>
<Suspense fallback={<div>loading...</div>}>
<Sub count={count} />
</Suspense>
<div>count is {count}</div>
</>
);
};
每过一秒会触发一次更新,将状态count更新为count => count + 1
在Sub中会发起异步请求,请求返回前,包裹Sub的Suspense会渲染fallback
// Sub内请求发起前
<div class=“sub”>I am sub, count is 0</div>
<div>count is 0</div>
// Sub内请求发起第1秒
<div>loading...</div>
<div>count is 1</div>
// Sub内请求发起第2秒
<div>loading...</div>
<div>count is 2</div>
// Sub内请求发起第3秒
<div>loading...</div>
<div>count is 3</div>
// Sub内请求成功后
<div class=“sub”>I am sub, request success, count is 4</div>
<div>count is 4</div>
请求Sub的任务(观察第一个div的变化) 改变count的任务(观察第二个div的变化)
一个无法解决的bug
Suspense对应的高优IO更新,简称u0
每秒产生的低优CPU更新,简称u1、u2、u3等
// u0优先级远大于u1、u2、u3...
u0.expirationTime >> u1.expirationTime > u2.expirationTime > …
u0优先级最高,则u1及之后的更新都需要等待u0执行完毕后再进行。
// Sub内请求发起前
<div class=“sub”>I am sub, count is 0</div>
<div>count is 0</div>
// Sub内请求发起第1秒
<div>loading...</div>
<div>count is 0</div>
// Sub内请求发起第2秒
<div>loading...</div>
<div>count is 0</div>
// Sub内请求发起第3秒
<div>loading...</div>
<div>count is 0</div>
// Sub内请求成功后
<div class=“sub”>I am sub, request success, count is 4</div>
<div>count is 4</div>
出现bug的原因
Lane调度算法
// 对应SyncLane,为最高优先级
0b0000000000000000000000000000001
// 对应InputContinuousLane
0b0000000000000000000000000000100
// 对应DefaultLane
0b0000000000000000000000000010000
// 对应IdleLane
0b0100000000000000000000000000000
// 对应OffscreenLane,为最低优先级
0b1000000000000000000000000000000
// 要使用的批次
let lanesForBatch = 0;
const laneA = 0b0000000000000000000000001000000;
const laneB = 0b0000000000000000000000000000001;
// 将laneA纳入批次中
lanesForBatch |= laneA;
// 将laneB纳入批次中
lanesForBatch |= laneB;
总结
选取优先级 选取批次
评论