React 调度
YuLong~W 人气:1异步调度
问题:由于对于大型的 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、调度流程
加载全部内容