likes
comments
collection
share

React原理:通俗易懂的 Fiber

作者站长头像
站长
· 阅读数 22

前言

React 16 之后,推出了 Fiber 架构,而 Fiber 具体是个什么东西,有很多优秀的文章进行解读,而本文在学习了各个大佬的文章后,尽力以最通俗易懂过的话来解释 Fiber。

React 16以前的老架构

在讲 Fiber 前,需要认识 React 16 前的架构

两层架构

React 16前的架构有两层:

  • Reconciler(协调器):对比新旧 VDOM 找不同
  • Renderer(渲染器):将最新的 VDOM 转 真实DOM 渲染到页面上。

流程

当组件更新时,会调用 Reconciler(协调器):

  1. JSX 转 VDOM
  2. 新旧 VDOM 作比较(老架构是 递归遍历 VDOM,不能中断)
  3. 递归结束,通知 Renderer(渲染器),将最新的 VDOM 渲染到页面上

缺点

React 16前,采用的是 递归遍历新旧 VDOM 树做对比,这会存在一个问题:递归时,如果 VDOM树层级很深,那么会长时间占用 JS 主线程,而 JS又是单线程的,且递归又是同步递归的,就会导致页面上的某些交互操作无法响应、动画卡顿等问题。所以为了解决这个问题,React 16后,新增了 Fiber 架构。

Fiber 架构

前面也说了,Fiber 架构的出现就是为了解决 数据量过大导致页面掉帧卡顿、不能及时响应操作的问题的,那么 Fiber 具体是个什么东西呢?

Fiber 的含义

  • 架构层面:异步可中断的方案,称为 Fiber Reconciler。React 16前没有 Fiber,递归调用栈,叫做 Stack Reconciler。
  • 数据结构层面:保存了组件状态、信息的特殊链表,其中比较重要的三个属性:return(指向父节点)、child(指向子节点)、sibling(指向兄弟节点)

Fiber 工作流程

上面我们提到,React 16前的 Stack Reconciler 的工作流程是:

  • Stack Reconciler 递归对比虚拟DOM找不同
  • Renderer 接到 Reconciler 的通知,渲染新的 VDOM。

而 Fiber 架构里面变为了三个步骤:

  • Scheduler(调度器):设置任务的优先级,优先级高的推入 Reconciler。
  • Reconciler:VDOM 转 Fiber,对变化的 Fiber 节点打上标记 flag。对可复用的节点打上修改的标记,剩余的旧节点打上删除标记,新节点打上新增标记。也就是在协调器这一步,就已经清楚了节点是增还是改还是删
  • Renderer:根据 Reconciler 打的标记,直接渲染(因为上一步已经知道 Fiber 节点是如何变化的,所以渲染的时候就会很快)

在 Fiber 架构下,更新的顺序变成了:

  • Scheduler 给每个更新任务赋予优先级
  • 优先级高的更新任务A,会被推入 Reconciler,VDOM 转 Fiber,新的 VDOM 和 旧的 Fiber 进行 diff 对比决定怎样生成新的 Fiber 。但如果此时有新的更高优先级的更新任务B 进入 Scheduler,那么 A 就会被中断B被推入 Reconciler,当 B 完成渲染后。新一轮的调度开始,A 是新一轮中优先级最高的,那 A 就继续推入 Reconciler 执行更新任务。
  • 重复以上的 可中断、可重复 步骤,直至所有更新任务完成渲染。

流程图如下:

React原理:通俗易懂的 Fiber

Fiber 数据结构

前面说了, Fiber 是个保存了组件状态、信息的特殊链表,其中三个属性 return、child、sibling 构成了链表的指向。

function App() {
    return (
        <div>
            <p></p>
            <a></a>
        </div>
    )
}

变成 Fiber 链表后:

React原理:通俗易懂的 Fiber

Fiber 双缓存

React 更新DOM 采用的是双缓存技术。React 中最多会存在两颗 Fiber树:

  • currentFiber:页面中显示的内容
  • workInProgressFiber:内存中正在重新构建的 Fiber树。

双缓存中:当 workInProgressFiber 在内存中构建完成后,React 会直接用它 替换掉 currentFiber,这样能快速更新 DOM。一旦 workInProgressFiber树 渲染在页面上后,它就会变成 currentFiber 树,也就是说 fiberRootNode 会指向它。

currentFiber 中有一个属性 alternate 指向它对应的 workInProgressFiber,同样,workInProgressFiber 也有一个属性 alternate 指向它对应的 currentFiber。也就是下面的这种结构:

React原理:通俗易懂的 Fiber

Fiber 的构建与更新

接下来通过图解,说明 Fiber 的 Mount(构建) 和 Update(更新)

Mount

  1. 创建 fiberRoot 和 rootFiberNode 两个节点, (fiberRoot是整个项目的根节点,只存在一个,rootFiber是应用的根节点,可能存在多个,例如多个 ReactDOM.render(<App />, document.getElementById("root"));创建多个应用节点。) 并根据 JSX 在内存中构建 workInProgressFiber 树。

React原理:通俗易懂的 Fiber

左侧是 currentFiber,右侧是 workInProgressFiber。

  1. workInProgressFiber 树构建完成后,将其变为 currentFiber树。

React原理:通俗易懂的 Fiber

Update

  1. 根据 currentFiber 在内存中构建 workInProgressFiber。

React原理:通俗易懂的 Fiber

构建 workInProgressFiber 是在createWorkInProgress中,它能创建或者服用Fiber

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {//区分是在mount时还是在update时
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
   
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;//复用属性
    workInProgress.type = current.type;
    workInProgress.flags = NoFlags;

    workInProgress.nextEffect = null;
    workInProgress.firstEffect = null;
    workInProgress.lastEffect = null;
	
    //...
  }

  workInProgress.childLanes = current.childLanes;//复用属性
  workInProgress.lanes = current.lanes;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;

  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : {
          lanes: currentDependencies.lanes,
          firstContext: currentDependencies.firstContext,
        };

  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;


  return workInProgress;
}
  1. 构建完后,将 workInProgressFiber 替换成新的 currentFiber。

React原理:通俗易懂的 Fiber

也就是说:jsx 改变,生成新的 VDOM, 用它和 旧的 Fiber(current Fiber)对比(diff算法),然后标记差异(是增加、修改、还是删除),形成一颗叫 workInProgress 的 Fiber树,然后将 fiberRoot 的 current 指向workInProgress树,此时 workInProgress 就变成了current Fiber

结语

以上内容如有错误,欢迎留言指出,一起进步💪,也欢迎大家一起讨论

参考文章