时间切片的实现和调度(原创2.6万字)

本人系一个惯用Vue的菜鸡,恰巧周末和大佬扯蛋,峰回路转谈到了fiber,被大佬疯狂鄙视...

大佬还和我吐槽了现在的忘了环境

百度是不可信的,百度到的东西出来广告其他都是出自同一个作者(大部分情况确实这样)

很多水文都是以 copy 的形式产生的,你看到的文章说不定已经过时好几个版本了(大部分情况确实这样)

于是本菜开始了 React Fiber 相关的读源码过程。为什么看 Fiber?因为 Vue 没有,Vue3 也没有,但是却被吹的很神奇。

本菜于编写时间于:2020/05/25,参考的当日源码版本 v16.13.1

Fiber的出现是为了解决什么问题? <略过一下>

首先必须要知道为什么会出现 Fiber

旧版本React同步更新:当React决定要加载或者更新组件树时,会做很多事,比如调用各个组件的生命周期函数,计算和比对Virtual DOM,最后更新DOM树。

举个栗子:更新一个组件需要1毫秒,如果要更新1000个组件,那就会耗时1秒,在这1秒的更新过程中,主线程都在专心运行更新操作。

而浏览器每间隔一定的时间重新绘制一下当前页面。一般来说这个频率是每秒60次。也就是说每16毫秒( 1 / 60 ≈ 0.0167 )浏览器会有一个周期性地重绘行为,这每16毫秒我们称为一帧。这一帧的时间里面浏览器做些什么事情呢:

执行JS。

计算Style。

构建布局模型(Layout)。

绘制图层样式(Paint)。

组合计算渲染呈现结果(Composite)。

如果这六个步骤中,任意一个步骤所占用的时间过长,总时间超过 16ms 了之后,用户也许就能看到卡顿。而上述栗子中组件同步更新耗时 1秒,意味着差不多用户卡顿了 1秒钟!!!(差不多 - -!)

因为JavaScript单线程的特点,每个同步任务不能耗时太长,不然就会让程序不会对其他输入作出相应,React的更新过程就是犯了这个禁忌,而React Fiber就是要改变现状。

什么是 Fiber <略过一下>

解决同步更新的方案之一就是时间切片:把更新过程碎片化,把一个耗时长的任务分成很多小片。执行非阻塞渲染,基于优先级应用更新以及在后台预渲染内容。

Fiber 就是由 performUnitOfWork(ps:后文详细讲述) 方法操控的 工作单元,作为一种数据结构,用于代表某些worker,换句话说,就是一个work单元,通过Fiber的架构,提供了一种跟踪,调度,暂停和中止工作的便捷方式。

Fiber的创建和使用过程:

来自render方法返回的每个React元素的数据被合并到fiber node树中

React为每个React元素创建了一个fiber node

与React元素不同,每次渲染过程,不会再重新创建fiber

随后的更新中,React重用fiber节点,并使用来自相应React元素的数据来更新必要的属性。

同时React 会维护一个 workInProgressTree 用于计算更新(双缓冲),可以认为是一颗表示当前工作进度的树。还有一颗表示已渲染界面的旧树,React就是一边和旧树比对,一边构建WIP树的。 alternate 指向旧树的同等节点。

PS:上文说的 workInProgress 属于 beginWork 流程了,如果要写下来差不多篇幅还会增加一倍,这就不详细说明了...(主要是本人懒又菜...)

Fiber的体系结构分为两个主要阶段:reconciliation(协调)/render 和 commit,

React 的 Reconciliation 阶段 <略过一下>

Reconciliation 阶段在 Fiber重构后 和旧版本思路差别不大, 只不过不会再递归去比对、而且不会马上提交变更。

涉及生命钩子

shouldComponentUpdate

componentWillMount(废弃)

componentWillReceiveProps(废弃)

componentWillUpdate(废弃)

static getDerivedStateFromProps

reconciliation 特性:

可以打断,在协调阶段如果时间片用完,React就会选择让出控制权。因为协调阶段执行的工作不会导致任何用户可见的变更,所以在这个阶段让出控制权不会有什么问题。

因为协调阶段可能被中断、恢复,甚至重做,React 协调阶段的生命周期钩子可能会被调用多次!, 例如 componentWillMount 可能会被调用两次。

因此协调阶段的生命周期钩子不能包含副作用,所以,该钩子就被废弃了

完成 reconciliation 过程。这里用的是 深度优先搜索(DFS),先处理子节点,再处理兄弟节点,直到循环完成。

React 的 Commit 阶段 <略过一下>

涉及生命钩子

componentDidMount

componentDidUpdate

componentWillUnmount(废弃)

getSnapshotBeforeUpdate

render 和 commit:不能暂停,会一直更新界面直到完成

Fiber 如何处理优先级?

对于UI来说需要考虑以下问题:

并不是所有的state更新都需要立即显示出来,比如:

屏幕之外的部分的更新并不是所有的更新优先级都是一样的

用户输入的响应优先级要比通过请求填充内容的响应优先级更高

理想情况下,对于某些高优先级的操作,应该是可以打断低优先级的操作执行的

所以,React 定义了一系列事件优先级

下面是优先级时间的源码

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfxjf.html