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时:

    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更新,让最初的更新利用在实在节点上