React源码系列2----React Fiber
React Fiber架构
Scheduler-Reconciler-Renderer
Fiber 架构
代数效应
代数效应用于将副作用从函数中分离,保持函数的纯粹性
代数效应在React中的应用
For example--->Hooks;对于useState,useReducer,useRef,我们不需要关注FunctionComponent的state在Hook中如何保存
function App() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count => count + 1)
}
return (
<button onClick={handleClick}>{count}</button>
)
}
代数效应与Generator
从
React15到React16Reconciler的架构从同步更新变更到了可中断的异步更新
异步可中断更新:更新的执行过程中随时可能会被中断(浏览器时间分片用尽或者更高优先级的任务插队),当可以继续执行的时候恢复之前执行的中间状态
Generator实现异步可中断更新
代数效应与Fiber
React Fiber: 类似于协程
React内部的一套状态更新机制。支持不同的优先级,可以更新和恢复,并且在恢复之后可以服用之前的中间状态- 其中每个任务更新单元为
React Element对应的Fiber节点---虚拟DOM
Fiber的实现原理
Fiber之前的架构导致的问题:Reconciler采用递归的方式去更新虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归就会占用线程大量的时间和资源,造成页面卡顿
React16将递归的无法中断的更新重构为异步的可中断更新,由此产生了新的Fiber架构
What is Fiber
- 作为架构来说,
React15的Reconciler采用递归的方式去执行,数据保存在递归调用栈中---Stack Reconciler。React16的Reconciler基于Fiber节点----Fiber Reconciler- 作为静态数据结构来说,每一个
Fiber节点对应一个React Element,保存了该组件的类型(class\FunctionComponent\HostComponent)、对应的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.statNode = null;
// 用于链接其他Fiber节点形成Fiber树
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
// 作为动态的动作单元的属性
this.penfingProps = pendingProps;
this.memoizedProps = null;
this.updateQuene = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
//调度优先级
this.lanes = NoLanes;
this.childLanes = NoLanes;
//指向该fiber在另一次更新时候对应的fiber,双缓存的时候链接current Fiber和workInProgress Fiber
this.alternate = null;
<!-- more -->
<!-- more -->
}
Fiber作为架构
每一个Fiber节点都有对应的
React Element,多个Fiber节点应该如何链接成树?
// 指向父级Fiber节点; 指节点执行完completeWork之后会返回的下一个节点
this.return = null;
// 指向子Fiber节点
this.child = null;
// 指向右边第一个兄弟节点
this.sibling = null;
如下示例:
function App() {
return (
<div>
hello
<span>React</span>
</div>
)
}
对应的Fiber树结构:

return指节点执行完completeWork之后会返回的下一个节点,子节点及其兄弟节点在完成工作之后会返回其父级节点
作为静态的数据结构
Fiber作为静态的数据结构,保存了组件的相关信息:
// Fiber对应的组件类型,FunctionComponent/Class/HostComponent...
this.tag = tag;
// key属性
this.key = key;
// 当FunctionComponent使用React.memo包裹的时候type不一样
this.elemenType = null;
// 对于FunctionComponent,指函数本身
// 对于ClassComponent,指class,
// 对于HostComponent,指DOM节点tagName
this.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;
Fiber的工作原理
Fiber节点构成Fiber树,Fiber树对应DOM树- 直接在内存中构建并直接替换的技术--->双缓存
- 双缓存技术----
Fiber树的构建与替换-->对应着DOM树的构建与更新
双缓存Fiber树
在React中最多存在两棵Fiber树。
- 当前屏幕上显示内容对应的
Fiber树称为Current Fiber树 - 正在内存中构建的
Fiber树,称为workInProgress Fiber树
current Fiber树中的Fiber节点称为current Fiber;同理workInProgress Fiber树中的Fiber节点称为workInProgress Fiber,他们之间通过上文讲过的alternate链接
currentFiber.alternate === workInProgressFiber;
workInProgress.alternate === currentFiber;
React应用的根节点通过current指针在不同的Fiber树的rootFiber之间切换来实现Fiber树的切换。- 当
workInProgress Fiber树构建完成交付给Renderer渲染页面上之后,应用的根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树变更为current Fiber 树 - 每一次的状态更新都会产生新的
workInProgress Fiber树,通过current与workInProgress的交换,完成DOM更新。以下为mount和update时的构建与替换流程
mount
example:
function App() {
const [count, setCount] = useState(0)
return (
<p onClick={() => setCount(count + 1)}>{count}</p>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
-
- 首次执行
ReactDOM.render会创建fiberRootNode(源码里面的fiberRoot)和rootFiber;其中fiberNodeRoot是整个应用根节点,rootFiber是<App />所在组件树的根节点 - 区分
fiberRootNode和rootFiber的原因在与,在应用中可以多次调用ReactDOM.render渲染不同的组件树,他们拥有不同的rootFiber。但是整个应用的根节点只有一个,就是fiberRootNode fiberRootNode的current会指向当前页面上已经渲染内容对应的Fiber树----current Fiber 树

- 首次执行
fiberRootNode.current = rootFiber
由于是首屏渲染,此时页面还没有挂载任何
DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(current Fiber树为空)
-
- 进入
render阶段,根据组件返回的JSX在内存中依次创建Fiber节点并链接在一起构建Fiber树,这个时候被称为workInProgress Fiber树 - 在构建
workInProgress Fiber树时会尝试复用current Fiber树中已经存在的Fiber 节点内的属性,在首屏渲染的时候只有rootFiber存在对应的current Fiber(rootFiber.alternate)
- 进入
Fiber render阶段:

-
- 上图所示的已经构建完成的
workInProgress Fiber树在commit阶段渲染到页面 - 此时页面中DOM被重新渲染,
current指针指向workInProgress Fiber树,使其成为Current Fiber树

- 上图所示的已经构建完成的
update
-
- 我们点击
p标签的时候会触发onclick。改变状态值,这个操作会开启一次新的render阶段并构建一个新的workInProgress Fiber树
图示如下:

- 与
mount的时候一样,workInProgress Fiber的创建可以复用current fiber树对应的节点数据
这个决定是否使用的过程就是diff算法
- 我们点击
-
workInProgress Fiber 树在render阶段完成构建之后进入commit阶段渲染到页面上。在渲染完毕之后,当前的workInProgress Fiber树变更为current Fiber树

Fiber树的构建和替换的过程会伴随着DOM的更新
React的Scheduler-Reconciler-Renderer架构体系
Reconciler工作的阶段被称作render阶段。因为在该阶段调用组件的render方法Renderer阶段被称为commit阶段《类似于git commit》,commit阶段会把render提交的信息渲染在页面上render与commit阶段的统称为work,也就是React的工作流程,如果任务处于Scheduler内部进行调度,不是work
JSX与Fiber节点
JSX是一种描述当前组件数据类型的数据结构,不包含schedule、reconcile、render所需要的相关信息- 组件在更新中的
优先级 - 组件的
state - 组件被打上用于Reconciler的标记
- 组件在更新中的
- 在组件
mount的时候,Reconciler根据JSX描述的组件内容生成对应的Fiber节点 - 在组件
update的时候,Reconciler将JSX与Fiber节点保存的数据进行对比,生成组件对应的Fiber节点,并根据对应的结果给Fiber节点打上标记