前文
我们知道每次setState会触发重新渲染,就会启动上面链接提到的render+commit渲染流程,
我们的应用可能会同时调用很多次setState,那怎么去处理他们的调用顺序?这就是本文所探讨的问题
react模式
- legacy 模式:
ReactDOM.render(<App />, rootNode)。这是当前 React 18之前使用的方式。当前没有计划删除本模式,但是这个模式可能不支持这些新功能。 - concurrent 模式:
ReactDOM.createRoot(rootNode).render(<App />)。 React 18的默认开发模式。这个模式开启了所有的新功能。(并发模式)
同步流程
react17及之前默认使用的是legacy模式
多次的setState按顺序处理,分多次进行render+commit渲染流程,
存在的问题
如果input输入过程中,input关联的state的变化引起了其他state的变化,两个 setState 一起发生,如果这个渲染流程中处理的 fiber 节点比较多,渲染一次就比较慢,这时候用户输入的内容可能就不能及时的渲染出来,用户就会感觉卡,体验不好。可能会影响到input输入的体感,会产生卡顿的感觉(这个我就有真实遇到过,后来是把某个颗粒化的列表元素组件进行了useCallback+React.memo缓存,减少了每次渲染处理的fiber节点)
并发流程
react18默认使用的是concurrent模式,首先并发是多个任务轮流使用一个cpu,并行是有多个cpu共同处理多个任务
- 同步模式是循环处理 fiber 节点,并发模式多了个 shouldYield 的判断,每 5ms 打断一次,也就是时间分片。并且之后会重新调度渲染。通过这种打断和恢复的方式实现了并发。
- 然后 Scheduler 可以根据优先级来对任务排序,这样就可以实现高优先级的更新先执行。
- 每次 setState 引起的渲染都是由 Scheduler 调度执行的,它维护了一个任务队列,上个任务执行完执行下个,被打断的任务会添加到Scheduler的任务队列里面
function workLoop() {
// 每处理一个fiber节点,都判断下是否打断
**// shouldYiled 方法就是判断待处理的任务队列有没有优先级更高的任务,有的话就先处理那边的 fiber,这边的先暂停一下。
//shouldYiled根据过期时间,每次开始处理时记录个时间,如果处理完这个 fiber 节点,时间超了,那就打断。这个就是时间分片
//优先级高低会影响 Scheduler 里的 taskQueue 的排序结果,但打断只会根据过期时间。**
while (wip && shouldYield()) {
performUnitOfWork();
}
if (!wip && wipRoot) {
commitRoot();
}
}
// workInProgress记录每个workLoop处理的fiber节点,然后根据workInProgress是否是null判断是否全部 fiber 节点都渲染完
// 根据wip 是不是 null判断是不是中断(中断的话wip不为null)
优先级调度
Schedular的优先级
图片from:神光 juejin.cn/post/717123…
Immediate 是离散的一些事件,比如 click、keydown、input 这种。
UserBlocking 是连续的一些事件,比如 scroll、drag、mouseover 这种。
然后是默认的优先级 NormalPriority、再就是低优先级 LowPriority,空闲优先级 IdlePriority。
lane模型
图片from:神光 juejin.cn/post/717123…
react事件优先级
DiscreteEventPriority 离散事件优先级
ContinuousEventPriority 连续事件优先级
DefaultEventPriority 默认事件优先级
IdleEventPriority 空闲时间优先级
react 内部有 31 种 Lane 优先级,但是调度 Scheduler 任务的时候,会先转成事件优先级,然后再转成 Scheduler 的 5 种优先级。
-
采用Lane 优先级的原因是因为,二进制位运算比较快
-
然后转换成事件优先级是因为能和Scheduler 的 5 种优先级对应上
-
为什么不直接转换成Schduler的五种优先级呢?
- 因为 Schduler 是分离的一个包了,它的优先级机制也是独立的,而lane优先级和事件优先级都是react自己的一套优先级机制
总结
- 同步模式就是按顺序依次渲染
- 并发模式就是在 workLoop 里通过 shouldYield 的判断来打断渲染,之后把剩下的节点加入 Schedule 调度,来恢复渲染。
- 时间分片的 workLoop + 优先级调度,这就是 React 并发机制的实现原理。这就是 React 并发机制的实现原理
参考
全文都参考自:juejin.cn/post/717123…