深入理解React调度(Scheduler)原理
目录
- 异步调度
- 时间分片
- 异步调度原理
- 总结
异步调度
问题:由于对于大型的 React 应用,会存在一次更新,递归遍历大量的虚拟 DOM ,造成占用 js 线程,使得浏览器没有时间去做一些动画效果,伴随项目越来越大,项目会越来越卡。
对比Vue:
Vue 有这 template 模版收集依赖的过程,轻松构建响应式,使得在一次更新中,Vue 能够迅速响应,找到需要更新的范围,然后以组件粒度更新组件,渲染视图。
React 中,一次更新 React 无法知道此次更新的波及范围,所以 React 选择从根节点开始 diff ,查找不同,更新这些不同。
解决:
把 React 的更新,交给浏览器自己控制,浏览器先执行绘制任务,空闲时间执行更新任务,解决了卡顿问题。即采用异步调度的方法。
时间分片
React让浏览器控制React更新:浏览器每执行一次事件循环都会:处理事件,执行 js,调用requestAnimation
,布局 Layout,绘制 Paint,在一次执行后,浏览器进入空闲时,可以执行更新任务
谷歌浏览器提供的一个 API, 在浏览器有空余的时间,浏览器就会调用 requestIdleCallback 的回调。
requestIdleCallback(callback,{ timeout })
- callback 回调。浏览器空余时间执行回调函数。
- timeout 超时时间。如果浏览器长时间没有空闲,那么回调就不会执行,为了解决这个问题,可以通过 requestIdleCallback 的第二个参数指定一个超时时间。
React 为了防止 requestIdleCallback
中的任务由于浏览器没有空闲时间而卡死,所以设置了 5 个优先级。
Immediate
-1 需要立刻执行。UserBlocking
250ms 超时时间250ms,一般指的是用户交互。Normal
5000ms 超时时间5s,不需要直观立即变化的任务,比如网络请求。Low
10000ms 超时时间10s,肯定要执行的任务,但是可以放在最后处理。Idle
一些没有必要的任务,可能不会执行。
模拟requestdleCallback
条件:
- 可以主动让出主线程,让浏览器去渲染视图。
- 一次事件循环只执行一次,因为执行一个以后,还会请求下一次的时间片。
宏任务:在下次事件循环中执行,不会阻塞浏览器更新。且浏览器一次只会执行一个宏任务。
1、采用setTimeout(fn, 0),间隔时间会变成 4 毫秒左右,不是最优选方案
2、采用MessageChannel 接口,允许开发者创建一个新的消息通道,并通过它的两个 MessagePort 属性发送数据。
在一次更新中,向浏览器请求执行更新任务,调用 requesetHostCallbcak
,将更新任务 函数callback赋值给 scheduleHostCallback
,port2 向 port1 发起 postMessage消息通知。
port1 会通过 onmessage,接受来自 port2 消息,执行更新任务 scheduleHostCallback
,执行完后,清空任务。
异步调度原理
React 发生一次更新,会统一走 ensureRootIsScheduled(调度应用)
对于 正常更新 会走 performSyncWorkOnRoot 逻辑,最后会走 workLoopSync
。
对于 低优先级的异步更新 会走 performConcurrentWorkOnRoot 逻辑,最后会走 workLoopConcurrent
。
区别:异步模式会调用一个 shouldYield()
,如果当前浏览器没有空余时间, shouldYield 会中止循环,直到浏览器有空闲时间后再继续遍历,从而达到终止渲染的目的。解决了一次性遍历大量的 fiber ,导致浏览器没有时间执行一些渲染任务,导致了页面卡顿。
1、scheduleCallback
更新任务、异步更新任务都是由调度器 scheduleCallback
统一调度的
正常更新任务:
scheduleCallback(Immediate,workLoopSync)
异步更新任务:
/* 计算超时等级,就是如上那五个等级 */ var priorityLevel = inferPriorityFromExpirationTime(currentTime, expirationTime); scheduleCallback(priorityLevel,workLoopConcurrent)
scheduleCallback()
函数执行过程
scheduleCallback 流程如下:
- 创建一个新的任务 newTask。
- 通过任务的开始时间( startTime ) 和 当前时间( currentTime ) 比较:当 startTime > currentTime, 说明未过期,存到 timerQueue,当 startTime <= currentTime,说明已过期, 存到 taskQueue。
- 如果任务没有过期,用
requestHostTimeout
延时执行handleTimeout
。 - 如果任务过期,并且没有调度中的任务,那么调度
requestHostCallback
。 - 本质上调度的是
flushWork
。
2、requestHostTimeout
通过 setTimeout 来进行延时指定时间的。
延时执行 handleTimeout
,cancelHostTimeout
用于清除当前的延时器。
3、handleTimeout
延时时间后,handleTimeout 会把任务重新放在 requestHostCallback 调度。
通过 advanceTimers
将 timerQueue 中过期的任务转移到 taskQueue 中。然后调用 requestHostCallback
调度过期的任务。
4、advanceTimers
如果任务已经过期,那么将 timerQueue 中的过期任务,放入 taskQueue。
5、flushWork
requestHostCallback ,放入 MessageChannel 中的回调函数是flushWork。
flushWork 如果有延时任务执行的话,那么会先暂停延时任务,然后调用 workLoop
,去真正执行超时的更新任务。
6、workLoop
workLoop 是调度中的 workLoop
React 的更新任务最后都是放在 taskQueue 中
workLoop 会依次更新过期任务队列中的任务
调度流程图
总结
1、异步调度原因
2、时间分片和 requestIdleCallback
3、异步调度原理
4、调度流程
到此这篇关于深入理解React 调度(Scheduler)原理的文章就介绍到这了,更多相关React 调度内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!