写点什么

面试官: 你是怎样理解 Fiber 的

作者:全栈潇晨
  • 2021 年 12 月 10 日
  • 本文字数:4040 字

    阅读完需:约 13 分钟

面试官:你是怎样理解 Fiber 的

hello,这里是潇晨,今天我们来聊一聊 Fiber。不知道大家面试的时候有没有遇到过和 react Fiber 相关的问题呢,这一类问题比较开放,但也是考察对 react 源码理解深度的问题,如果面试高级前端岗,恰巧你平时用的是 react,那这道面试题是你必需要会的一道。

大型应用为什么会慢

那之前的应用为什么会慢呢,传统的前端应用例如 js 原生或者 jquery 应用,在构建复杂的大型应用的时候,各种页面之前的相互操作和更新很有可能会引起页面的重绘或重排列,而频繁操作这些 dom 其实是非常消耗性能的



在看下图,这是一个节点上的属性,可以看到一个节点上的属性是非常多的,在复杂应用中,操作这些属性的时候可能一不小心就会引起节点大量的更新,那如何提高应用的性能呢?


const div = document.createElement('div');let str = ''for(let k in div){  str+=','+k}console.log(str)
复制代码


为什么会出现 Fiber

react 从 15 版本开始,到现在的 17,以及快出来的 18,内部经历了非常大的变化,这一切都是围绕着一个目标进行的,这个目标是异步可中断的更新,而这个目的的最终结果是为了构建快速响应的应用。


复杂应用在更新的时候可能会更新大量的 dom,所以 react 在应用层和 dom 层之间增加了一层 Fiber,而 Fiber 是在内存中工作的,所以在更新的时候只需要在内存中进行 dom 更新的比较,最后再应用到需要更新真实节点上


这就引出了一个对比新老节点的过程,而对比两棵树的计算其实是非常消耗性能的,react 提出了 diff 算法来降低对比的复杂度,具体 diff 的过程可以参考往期文章 diff算法


但是面对越来越复杂的应用,diff 算法消耗的时间片还是很长,在没做出优化的情况下,react 在进行 Fiber 的对比和更新节点上的状态的时候依然力不从心,


  • 在 react15 之前,这个对比的过程被称之为 stack reconcile,它的对比方式是‘一条路走到黑’,也就是说这个对比的过程是不能被中断的,这会出现什么情况呢,比如在页面渲染一个比较消耗性能操作,如果这个时候如果用户进行一些操作就会出现卡顿,应用就会显得不流畅。

  • react16 之后出现了 scheduler,以及 react17 的 Lane 模型,它们可以配合着工作,将比较耗时的任务按照 Fiber 节点划分成工作单元,并且遍历 Fiber 树计算或者更新节点上的状态可以被中断、继续,以及可以被高优先级的任务打断,比如用户触发的更新就是一个高优先级的任务,高优先级的任务优先执行,应用就不会太卡顿。

什么是 Fiber

这就是 react 所要做的事情了,react 创新的提出了 jsx,声明式地描述页面呈现的效果,jsx 会被 babel 经过 ast 解析成 React.createElement,而 React.createElement 函数执行之后就是 jsx 对象或者说是 virtual-dom


  • 在 mount 的时候,也就是首次渲染的时候,render 阶段会根据 jsx 对象生成新的 Fiber 节点,然后这些 Fiber 节点会被标记成带有‘Placement’的副作用,说明它们是新增的节点,需要被插入到真实节点中,在 commit 阶段就会操作真实节点,将它们插入到 dom 树中。

  • 在 update 的时候,也就是应用触发更新的时候,render 阶段会根据最新的 jsx 和老的 Fiber 进行对比,生成新的 Fiber,这些 Fiber 会带有各种副作用,比如‘Deletion’、‘Update’、‘Placement’等,这一个对比的过程就是diff算法 ,在 commit 阶段会操作真实节点,执行相应的副作用。


如果对 render 阶段和 commit 阶段不了解的可以查看往期文章


8.render阶段


10.commit阶段



Fiber 有比较多的含义,他可以从以下几个角度理解:


  • 工作单元 任务分解 :Fiber 最重要的功能就是作为工作单元,保存原生节点或者组件节点对应信息(包括优先级),这些节点通过指针的形似形成 Fiber 树

  • 增量渲染:通过 jsx 对象和 current Fiber 的对比,生成最小的差异补丁,应用到真实节点上

  • 根据优先级暂停、继续、排列优先级: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或者class  this.stateNode = null;//真实dom节点
//作为fiber数架构 连接成fiber树 this.return = null;//指向父节点 this.child = null;//指向child this.sibling = null;//指向兄弟节点 this.index = 0;
this.ref = null;
//用作为工作单元 来计算state this.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;}
复制代码

Fiber 是怎样工作的

现在我们知道了 Fiber 可以保存真实的 dom,真实 dom 对应在内存中的 Fiber 节点会形成 Fiber 树,这颗 Fiber 树在 react 中叫 current Fiber,也就是当前 dom 树对应的 Fiber 树,而正在构建 Fiber 树叫 workInProgress Fiber,这两颗树的节点通过 alternate 相连.


function App() {  return (    <>      <h1>        <p>count</p> xiaochen      </h1>    </>  )}
ReactDOM.render(<App />, document.getElementById("root"));
复制代码



构建 workInProgress Fiber 发生在 createWorkInProgress 中,它能创建或者服用 Fiber


//ReactFiber.old.jsexport 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;}
复制代码


  • 在 mount 时:会创建 fiberRoot 和 rootFiber,然后根据 jsx 对象创建 Fiber 节点,节点连接成 current Fiber 树。


  • 在 update 时:会根据新的状态形成的 jsx(ClassComponent 的 render 或者 FuncComponent 的返回值)和 current Fiber 对比形(diff 算法)成一颗叫 workInProgress 的 Fiber 树,然后将 fiberRoot 的 current 指向 workInProgress 树,此时 workInProgress 就变成了 current Fiber。fiberRoot:指整个应用的根节点,只存在一个

  • fiberRoot:指整个应用的根节点,只存在一个

    rootFiber:ReactDOM.render 或者 ReactDOM.unstable_createRoot 创建出来的应用的节点,可以存在多个。


我们现在知道了存在 current Fiber 和 workInProgress Fiber 两颗 Fiber 树,Fiber 双缓存指的就是,在经过 reconcile(diff)形成了新的 workInProgress Fiber 然后将 workInProgress Fiber 切换成 current Fiber 应用到真实 dom 中,存在双 Fiber 的好处是在内存中形成视图的描述,在最后应用到 dom 中,减少了对 dom 的操作。


现在来看看 Fiber 双缓存创建的过程图


  • mount 时:

  • 刚开始只创建了 fiberRoot 和 rootFiber 两个节点

  • 然后根据 jsx 创建 workInProgress Fiber:

  • 把 workInProgress Fiber 切换成 current Fiber

  • update 时

  • 根据 current Fiber 创建 workInProgress Fiber

  • 把 workInProgress Fiber 切换成 current Fiber


为什么 Fiber 能提升效率

Fiber 是一个 js 对象,能承载节点信息、优先级、updateQueue,同时它还是一个工作单元。


  1. Fiber 双缓存可以在构建好 wip Fiber 树之后切换成 current Fiber,内存中直接一次性切换,提高了性能

  2. Fiber 的存在使异步可中断的更新成为了可能,作为工作单元,可以在时间片内执行工作,没时间了交还执行权给浏览器,下次时间片继续执行之前暂停之后返回的 Fiber

  3. Fiber 可以在 reconcile 的时候进行相应的 diff 更新,让最后的更新应用在真实节点上

视频讲解(高效学习):点击学习

往期 react 源码解析文章:

1.开篇介绍和面试题


2.react的设计理念


3.react源码架构


4.源码目录结构和调试


5.jsx&核心api


6.legacy和concurrent模式入口函数


7.Fiber架构


8.render阶段


9.diff算法


10.commit阶段


11.生命周期


12.状态更新流程


13.hooks源码


14.手写hooks


15.scheduler&Lane


16.concurrent模式


17.context


18事件系统


19.手写迷你版react


20.总结&第一章的面试题解答

用户头像

全栈潇晨

关注

还未添加个人签名 2021.02.17 加入

还未添加个人简介

评论

发布
暂无评论
面试官:你是怎样理解Fiber的