乐趣区

关于react.js:面试官你是怎样理解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.js
function 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.js
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;
}
  • 在 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 时:

    1. 刚开始只创立了 fiberRoot 和 rootFiber 两个节点
    2. 而后依据 jsx 创立 workInProgress Fiber:
    3. 把 workInProgress Fiber 切换成 current Fiber
  • update 时

    1. 依据 current Fiber 创立 workInProgress Fiber
    2. 把 workInProgress Fiber 切换成 current Fiber

为什么 Fiber 能晋升效率

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

  1. Fiber 双缓存能够在构建好 wip Fiber 树之后切换成 current Fiber,内存中间接一次性切换,进步了性能
  2. Fiber 的存在使异步可中断的更新成为了可能,作为工作单元,能够在工夫片内执行工作,没工夫了交还执行权给浏览器,下次工夫片继续执行之前暂停之后返回的 Fiber
  3. Fiber 能够在 reconcile 的时候进行相应的 diff 更新,让最初的更新利用在实在节点上
退出移动版