Fiber架构
Fiber并不是计算机术语中的新名词,他的中文翻译叫做纤程,与进程(Process)、线程(Thread)、协程(Coroutine)同为程序执行过程。
在很多文章中将纤程理解为协程的一种实现。在JS中,协程的实现便是Generator。
所以,我们可以将纤程(Fiber)、协程(Generator)理解为代数效应思想在JS中的体现。
React Fiber可以理解为:
React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态。
其中每个任务更新单元为React Element对应的Fiber节点。
Fiber架构心智模型
React核心团队成员Sebastian Markbåge (opens new window)(React Hooks的发明者)曾说:我们在React中做的就是践行代数效应(Algebraic Effects)。
代数效应
代数效应能够将副作用从函数逻辑中分离,使函数关注点保持纯粹。
就比如我们平时用await来等待一个值的返回
async function getData(){ const res = await loadData(); return res;}代数效应相当于,我关注的是 await loadData()能给我什么东西,而不是关注他里头是异步还是同步,怎么处理这个数据的;
代数效应 in React
在react中有个结合Suspense的例子
而我们日常用hooks,例如useState什么的,我们不需要关注函数组件的这个state状态是怎么处理的,我们只要知道这个东西能干嘛就行。
为什么不用Generator
新老架构那片说过,React16之后引入了一个 scheduler(调度器),并且重构了Reconciler(协调器),就是为了将react的老一套同步更新 的架构变成 **异步可中断 **的
异步可中断更新可以理解为:更新在执行过程中可能会被打断(浏览器时间分片用尽或有更高优任务插队),当可以继续执行时恢复之前执行的中间状态。
缺点:
generator它具有传染性和async await一样,就像你用async的函数那东西,你需要写await一样- 中间状态上下文相关,可以看看这个 解释
Fiber实现原理
Fiber含义
首先我们要明白:
- react15的时候采用递归方式去执行,数据保存在递归调用栈中,故被称为
stack reconciler - react16 的reconciler 基于
fiber节点实现的,故称为fiber reconciler - 一个
React Element对应一个Fiber节点- 作为静态结构理解 --- 保存了该组件类型(原生/类组件/函数组件/…),以及对应dom节点信息
- 作为动态结构理解 --- 每个
Fiber保存了本次更新中该组件改变的状态,要执行的工作(插入/删除/更新…)
Fiber数据结构
function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,) { // 作为静态数据结构的属性 this.tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null;
// 用于连接其他Fiber节点形成Fiber树 this.return = null; this.child = null; this.sibling = null; this.index = 0;
this.ref = null;
this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.dependencies = null;
this.mode = mode;
// 作为动态的工作单元的属性 this.effectTag = NoEffect; this.subtreeTag = NoSubtreeEffect; this.deletions = null; this.nextEffect = null;
this.firstEffect = null; this.lastEffect = null;
// 调度优先级相关 this.lanes = NoLanes; this.childLanes = NoLanes;
// 指向该fiber在另一次更新时对应的fiber, current tree中的fiber节点和workinprogress tree的fiber节点连接 this.alternate = null; }Fiber节点之间关系
Fiber节点之间是通过以下三个属性建立起连接的
// 指向父级Fiber节点this.return = null;// 指向子Fiber节点this.child = null;// 指向右边第一个兄弟Fiber节点this.sibling = null;比如下面的代码和对应的Fiber树如下:
function App(){ return ( <div> <span>hello world</span> <a> weng <span/> </a> </div> )}
这里需要提一下,为什么父级指针叫做
return而不是parent或者father呢?因为作为一个工作单元,return指节点执行完completeWork(后面会说)后会返回的下一个节点。子Fiber节点及其兄弟节点完成工作后会返回其父级节点,所以用return指代父级节点。
作为静态的数据结构
作为一种静态的数据结构,保存了组件相关的信息:
// Fiber对应组件的类型 Function/Class/Host...this.tag = tag;// key属性this.key = key;// 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹this.elementType = null;// 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagNamethis.type = null;// Fiber对应的真实DOM节点this.stateNode = null;作为动态的工作单元
作为动态的工作单元,Fiber中如下参数保存了本次更新相关的信息,我们会在后续的更新流程中使用到具体属性时再详细介绍
// 保存本次更新造成的状态改变相关信息this.pendingProps = pendingProps;this.memoizedProps = null;this.updateQueue = null;this.memoizedState = null;this.dependencies = null;
this.mode = mode;
// 保存本次更新会造成的DOM操作this.effectTag = NoEffect;this.nextEffect = null;
this.firstEffect = null;this.lastEffect = null;如下两个字段保存调度优先级相关的信息,会在学习Scheduler时说
// 调度优先级相关this.lanes = NoLanes;this.childLanes = NoLanes;Fiber工作原理
首先,上面说了,Fiber节点直接会构成一棵Fiber树,并且存有节点以及各种信息每个Fiber。
所以一个Fiber是根据一个dom或者说React Element的出来的,那么树的结构也和dom或者组件树相同。
主要 更新 工作原理,这里用到了一个叫做 双缓存的技术
what is 双缓存?
当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。
如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。
为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。
这种在内存中构建并直接替换的技术叫做双缓存 。
React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。
Fiber树和双缓存
React中最多会同时存在两棵Fiber树
Current Fiber树:当前显示在屏幕面前的树workInProgress Fiber树:正在内存中构建的树
current Fiber树中的Fiber节点被称为current fiber,workInProgress Fiber树中的Fiber节点被称为workInProgress fiber,他们通过alternate属性连接。
currentFiber.alternate === workInProgressFiber;workInProgressFiber.alternate === currentFiber;React应用的根节点通过使current指针在不同Fiber树的rootFiber间切换来完成current Fiber树指向的切换。
即当workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树。
每次状态更新都会产生新的workInProgress Fiber树,通过current与workInProgress的替换,完成DOM更新。
mount时候
考虑如下例子:
function App() { const [num, add] = useState(0); return ( <p onClick={() => add(num + 1)}>{num}</p> )}
ReactDOM.render(<App/>, document.getElementById('root'));- 首次执行
ReactDOM.render会创建fiberRootNode(源码中叫fiberRoot)和rootFiber。其中fiberRootNode是整个应用的根节点,rootFiber是“所在组件树的根节点。
之所以要区分fiberRootNode与rootFiber,是因为在应用中我们可以多次调用ReactDOM.render渲染不同的组件树,他们会拥有不同的rootFiber。但是整个应用的根节点只有一个,那就是fiberRootNode。
fiberRootNode的current会指向当前页面上已渲染内容对应Fiber树,即current Fiber树。

由于是首屏渲染,页面中还没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即current Fiber树为空)。
- 接下来进入
render阶段,根据组件返回的JSX在内存中依次创建Fiber节点并连接在一起构建Fiber树,被称为workInProgress Fiber树。(下图中右侧为内存中构建的树,左侧为页面显示的树)
在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性,在首屏渲染时只有rootFiber存在对应的current fiber(即rootFiber.alternate)。


update时
- 接下来我们点击
p节点触发状态改变,这会开启一次新的render阶段并构建一棵新的workInProgress Fiber 树。

这个决定是否复用的过程就是Diff算法,后面会说
workInProgress Fiber 树在render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,workInProgress Fiber 树变为current Fiber 树。

查看源码中的FiberRootNode
上面曾说到,我们首次创建react的应用的时候会创建整个应用的一个根节点 叫做 FiberRootNode
然后每次调用render方法都会创建当前组件的一个根节点叫做 rootFiber
这里我们验证以下这个 FiberRootNode

图中圈出的部分就是react应用首次运行时候,创建一个**应用根节点FiberRootNode**的过程
我们顺着调用栈找到这个创建的调用方法 creatFiberRoot --- createFiber(创建应用根的方法)

找到源码,打个断点

刷新页面之后

发现首次渲染,创建fiberRoot节点,这个tag为3,那么代表什么意思呢?
我们可以在它右边的调用栈中找到上层函数createHostRootFiber

其实发现这个tag,也就是这里的HostRoot值为3