Fiber既是 react 定义的一种数据结构,也是我们常说的虚拟 DOM
由一个个Fiber节点,构成的树就是Fiber Tree
为什么要设计 Fiber 数据结构
React15 在 render 阶段的 reconcile 是不可打断的,这会在进行大量节点的 reconcile 时可能产生卡顿,因为浏览器所有的时间都交给了 js 执行,而JS线程与GUI渲染线程互斥。
为此 React16 之后就有了 scheduler 进行时间片的调度,给每个 task(工作单元)一定的时间,如果在这个时间内没执行完,也要交出执行权给浏览器进行绘制和重排。
而异步可中断的更新需要一定的数据结构,在内存中来保存工作单元的信息,所以出现了 Fiber 数据结构。
Fiber 的作用
- 工作单元,任务分解 :Fiber 最重要的功能就是作为工作单元,保存原生节点或者组件节点对应信息(包括优先级)。这些节点通过指针的形式,构成 Fiber 树
 - 增量渲染:通过 jsx 对象和 current Fiber 的对比,生成最小的差异补丁
(Flags),应用到真实节点上 - 根据优先级暂停、继续、排列优先级:Fiber 节点上保存了优先级,能通过不同节点优先级的对比,达到任务的暂停、继续、排列优先级等能力,也为上层实现批量更新、Suspense 提供了基础
 - 保存状态:因为 Fiber 能保存状态和更新的信息,所以就能实现函数组件的状态更新,也就是 hooks
 
Fiber 长什么样
Fiber 节点的构建函数如下:
//ReactFiber.old.jsfunction FiberNode(tag: WorkTag,pendingProps: mixed,key: null | string,mode: TypeOfMode,) {//作为静态的数据结构 保存节点的信息this.tag = tag; //对应组件的类型this.key = key; //key 属性this.elementType = null;//元素类型this.type = null; //func 或者 classthis.stateNode = null; //真实 dom 节点//作为 fiber 树架构 连接成 fiber 树this.return = null; //指向父节点this.child = null; //指向childthis.sibling = null; //指向兄弟节点this.index = 0;this.ref = null;//用作为工作单元 来计算statethis.pendingProps = pendingProps;this.memoizedProps = null;this.updateQueue = null;this.memoizedState = null;this.dependencies = null;this.mode = mode;//effect相关this.effectTag = NoEffect;this.nextEffect = null;this.firstEffect = null;this.lastEffect = null;//优先级相关的属性this.lanes = NoLanes;this.childLanes = NoLanes;//current和workInProgress的指针this.alternate = null;}
举个例子
function App() {return (<div>i am<span>KaSong</span></div>)}
Fiber 之双缓存
什么是双缓存
当我们用 canvas 绘制动画,每一帧绘制前都会调用 ctx.clearRect 清除上一帧的画面。
如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。
为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。
这种在内存中构建并直接替换的技术叫做双缓存。
React 使用“双缓存”来完成 Fiber 树的构建与替换——对应着DOM树的创建与更新。
双缓存 Fiber 树
在 React 中最多会同时存在两棵 Fiber 树
- 在屏幕上显示的 DOM 元素对应的 Fiber 树称为 current Fiber 树
- current Fiber 树 中的 Fiber 节点 被称为 
current fiber 
 - current Fiber 树 中的 Fiber 节点 被称为 
 - 正在内存中构建的 Fiber 树称为 workInProgress Fiber 树
- workInProgress Fiber 树 中的 Fiber 节点 被称为 
workInProgress fiber 
 - workInProgress Fiber 树 中的 Fiber 节点 被称为 
 
两棵树的 Fiber 节点,通过alternate 属性连接
currentFiber.alternate === workInProgressFiber;workInProgressFiber.alternate === currentFiber;
workInProgress Fiber 树 构建完成交给 Renderer 渲染在页面上后,把根节点(fiberRootNode)的 current 指针指向 workInProgress Fiber 树,此时 workInProgress Fiber树 就变为 current Fiber 树。
每次状态更新都会产生新的 workInProgress Fiber 树,通过 current 与 workInProgress 的替换,完成 DOM 更新。
接下来我们以具体例子讲解 mount 时、update 时的构建/替换流程。
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是<App/>所在组件树的根节点。由于是首屏渲染,页面中还没有挂载任何 DOM,所以
fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即 current Fiber 树为空)
 - 其中
 

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

- 图中右侧已构建完的 workInProgress Fiber 树 在 commit 阶段 渲染到页面。
此时 DOM 更新为右侧树对应的样子。
fiberRootNode的current指针指向 workInProgress Fiber树 使其变为 current Fiber 树。 
Update 时
- 接下来我们点击 p 节点触发状态改变,这会开启一次新的 render 阶段并构建一棵新的 workInProgress Fiber 树。
 
和 mount 时一样,workInProgress fiber 的创建可以复用 current Fiber 树 对应的节点数据。
这个决定是否复用的过程就是 Diff 算法

- workInProgress Fiber 树 在 render 阶段完成构建后,进入 commit 阶段渲染到页面上。渲染完毕后,workInProgress Fiber 树 变为 current Fiber 树。
 

