本人系一个惯用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 定义了一系列事件优先级
下面是优先级时间的源码