React Scheduler 怎样实现时间切片和优先级调度?
先理解原理和示例,再练习完整口述与追问。
回答要点
Scheduler 维护按过期时间排序的任务小顶堆,用 MessageChannel 在宏任务中执行工作循环,每个时间片约 5ms,超时就让出主线程,下一个宏任务继续。React 用 Lane 位掩码表示更新优先级,用户输入为同步或高优先级,Transition 为低优先级,高优先级更新可以打断正在进行的低优先级渲染。
原理拆解
不用 requestIdleCallback,是因为它触发频率不稳定、后台标签页几乎不执行,且兼容性差;不用 setTimeout(0),是因为嵌套多层后浏览器会把延迟限制到至少 4ms。MessageChannel 的消息作为宏任务调度,开销小,不阻塞渲染;不支持时退回 setTimeout。
Scheduler 的优先级(Immediate、UserBlocking、Normal、Low、Idle)对应不同的超时时间,过期的任务即使超出时间片也会同步执行,防止饥饿。React 调和阶段的 workLoopConcurrent 每处理一个 Fiber 就检查 shouldYield();提交阶段不可中断。被打断的低优先级渲染会丢弃半成品,在高优先级更新完成后基于最新状态重新开始。
示例与推演
const [query, setQuery] = useState('')
const [isPending, startTransition] = useTransition()
function onChange(event) {
setQuery(event.target.value) // 高优先级:输入框立刻更新
startTransition(() => setFilter(event.target.value)) // 低优先级:大列表筛选可被打断
}
连续输入时,列表的渲染不断被新输入打断并重新开始,输入框保持流畅;isPending 可用于显示加载指示。
易错点
时间切片不能拆分单个组件内部的同步计算,一个组件渲染需要 200ms 仍会阻塞;“5ms”是实现细节,不同版本可能调整。
参考回答可直接练习口述
Scheduler 用小顶堆按过期时间管理任务,通过 MessageChannel 在宏任务中执行工作循环,每个时间片大约 5ms,到时间就让出主线程给渲染和输入,下一轮继续。React 用 Lane 表示更新优先级,输入是高优先级,startTransition 包裹的是低优先级,高优先级更新能打断低优先级渲染,后者稍后重新开始;任务过期会强制执行防止饿死。但它切不开单个组件内部的重计算。