fiber架构的设计理念
1、fiber概述
在现有React中,更新过程是同步的,这可能会导致性能问题。
当React决定要加载或者更新组件树时,会做很多事,比如调用各个组件的生命周期函数,计算和比对Virtual DOM,最后更新DOM树,这整个过程是同步进行的,也就是说只要一个加载或者更新过程开始,那React就会一鼓作气运行到底,中途绝不停歇。在此过程中浏览器渲染引擎处于挂起的状态,无法进行任何渲染,浏览器无法执行任何其他的任务,调用栈如下图
破解javascript中同步操作时间过长的方法其实很简单——分片。
把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。
React Fiber把更新过程碎片化,执行过程如下面的图所示,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。
有了分片之后,更新过程的调用栈如下图所示,中间每一个波谷代表深入某个分片的执行过程,每个波峰就是一个分片执行结束交还控制权的时机。
维护每一个分片的数据结构,就是Fib
Fiber = {
tag, // 标记不同的组件类型
key, // ReactElement里面的key
elementType, // ReactElement.type,也就是我们调用`createElement`的第一个参数
type, // 一般是`function`或者`class`
stateNode, // 在浏览器环境,就是DOM节点
return, // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
child, // 单链表树结构,指向自己的第一个子节点
sibling, // 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
index,
ref, // ref属性
pendingProps, // 即将更新的props
memoizedProps, // 更新props
memoizedState, // 当前状态
dependencies, // 一个列表,存放这个Fiber依赖的context
flags, // Effect,用来记录当前fiber的tag,插入、删除、更新的状态
subtreeFlags,
deletions,
updateQueue, // 该Fiber对应的组件产生的Update会存放在这个队列里面
nextEffect, // 单链表用来快速查找下一下side effect
firstEffect, // 自述中第一个side effect
lastEffect, // 子树中最后一个side effect
lanes, // 当前Fiber更新的优先级
childLanes,
// 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber
// 我们称他为`current <==> workInProgress`
// 在渲染完成之后他们会交换位置
alternate,
// 用来描述当前Fiber和他子树的`Bitfield`
// 共存的模式表示这个子树是否默认是异步渲染的
// Fiber被创建的时候他会继承父Fiber
// 其他的标识也可以在创建的时候被设置
// 但是在创建之后不应该再被修改,特别是他的子Fiber创建之前
mode,
// 其余为调试相关的,收集每个Fiber和子树渲染时间的
//...
}
在react-hooks源码解析中我们主要涉及到memoizedState、updateQueue、flags字段。
setState具体实现
(1)首次渲染主要是初始化hook,将初始值存入hook内,将hook插入到fiber.memoizedState的末尾。
(2)setCount主要是将更新信息插入到hook.queue.penging的末尾。这里注意一下为什么没有直接更新hook.memoizedState呢?答案是react是批量更新的,需要将更新信息先存储下来,等到合适的时机统一计算更新。
(3)再次渲染主要是根据setCount存储的更新信息来计算最新的state。
为什么只能在函数最外层调用 Hook?为什么不要在循环、条件判断或者子函数中调用?
react 会生成一棵组件树(或Fiber 单链表)将hook插入到fiber.memoizedState的末尾。通过单链表的 next指针 按顺序串联所有的 hook。 所有memoizedState 数组是按 hook定义的顺序来放置数据的,如果 hook 顺序变化,memoizedState 并不会感知到。
双缓存Fiber树解决了当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏问题
React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。
在React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树。当应用程序状态发生更改,并需要更新 UI 时,React Fiber 首先在 Work Buffer 中执行所有渲染操作,以避免将中间状态呈现在屏幕上。一旦 Work Buffer 中的所有渲染操作完成,React Fiber 将当前缓存与工作缓存进行切换,即将 Work Buffer 设置为当前缓存,以此来更新屏幕上的 UI。