在本文之前说了 react15的毛病,这里来说下16版本是怎么修复的。

我之前的一篇文章 写过mini-react-fiber版本,那是一个简易版。

咱们这里剖析先做总结,而后依据总结的流程来看源码,这里次要就是串通主流程。

大架构:

首先从react源码层面执行,宏观角度来看,它实际上分为了两局部。

  1. render阶段:次要就是用来生成新的fiber树并diff出有变动的节点。
  2. commit阶段:获取到render阶段中diff进去的产生了变动的节点的fiber,通过原生api更新页面。

也就是说,在render阶段中,只会做一些简单的运算,并不会真正的操作页面(在内存中做 新旧 fiber对象比对,找出更新的fiber节点,或者是首次加载时 生成组装html片段),这一阶段是能够被打断的(初始渲染时不会被打断,因为要让用户尽快看到界面),也就是说在react里的工夫分片的概念,分的就是简单运算的局部也就是这里,在这里这个render阶段也就是说可能会被高优先级的工作(例如界面事件)打断。

直到commit阶段才会去通过js的原生api去批改dom,commit阶段是不能够被打断的,因为commit阶段是渲染阶段,它如果也是能够被打断的话,不一次性更新进去的话,就会呈现 相似网速过慢时 图片 迟缓加载的成果,和咱们的要求不符,所以commit阶段是同步的。

本质上我原来那篇mini-react-fiber文章也是遵循了react的两大阶段。

小架构:

当初咱们来认真说下react外部的架构划分:
react外部架构理论能够分成三层:

  1. 调度层Scheduler:调度工作的优先级,高优工作优先进入协调器
  2. 协调层Reconciler:构建 Fiber 数据结构,比对 Fiber 对象找出差别, 记录 Fiber 对象要进行的 DOM 操作(初始加载的时候,负责组装html片段)
  3. 渲染层Renderer:负责将发生变化的局部渲染到页面上

Scheduler

我后面比照15-16的时候讲过 16为了解决 vnode过大stack递归堆栈问题。引入了工作优先级 和 工作可中断的概念。 我那个繁难版本是通过window 对象中 requestIdleCallback API实现的,它能够利用浏览器的闲暇工夫执行工作,然而它本身也存在一些问题,比如说并不是所有的浏览器都反对它,而且它的触发频率也不是很稳固,所以 React 最终放弃了 requestIdleCallback 的应用。

在 React 中,官网实现了本人的任务调度库,这个库就叫做 Scheduler。它也能够实现在浏览器闲暇时执行工作,而且还能够设置工作的优先级,高优先级工作先执行,低优先级工作后执行。

Scheduler 存储在它源码的 packages/scheduler 文件夹中。

Reconciler

在 React 15 的版本中,协调器和渲染器交替执行,即找到了差别就间接更新差别。在 React 16 的版本中,这种状况产生了变动,协调器和渲染器不再交替执行。协调器负责找出差别,在所有差别找出之后,对立交给渲染器进行 DOM 的更新。也就是说协调器的次要工作就是找出差别局部,并为差别打上标记。

Renderer

渲染器依据协调器为 Fiber 节点打的标记,同步执行对应的DOM操作。

既然比对的过程从递归变成了能够中断的循环,那么 React 是如何解决中断更新时 DOM 渲染不齐全的问题呢?

其实基本就不存在这个问题,因为在整个过程中,调度器和协调器的工作是在内存中实现的是能够被打断的,渲染器的工作被设定成不能够被打断,所以不存在DOM 渲染不齐全的问题。

这样和咱们后面说的两个大阶段比照的话,Scheduler和Reconciler都能够归属到后面的render阶段,Scheduler负责工作优先级调度 Reconciler负责依据进入的工作来组装比照fiber构造,这个过程里高优先级能够打断低优先级,协调器 生产比照fiber也是能够被打断的, 也就验证了render能够被打断这一说法。

Renderer阶段对应大阶段就是咱们说的commit阶段,这个阶段渲染器 工作是不能够被打断的,它负责渲染更新界面。

而后咱们晓得了这个过程之后,大体来捋一下加载 和更新的流程,后续再贴代码 具体的地位。

加载

  1. 首先是react中有一个函数createElement 创立生成 虚构dom,后续会依据这个 vNode来生成对应的fiber构造。
  2. 进入流程 咱们调用render办法来进行首次渲染,首先react外部有一个reactRoot对象,而后 root对象外部外部有一个_internalRoot 外面存储的 fiberRoot,这个reactRoot对象是整个利用的根,每次react开始调度,不论是初始渲染还是setState都是从根开始的。
  3. 首次渲染会已最高级优先级来执行,因为要尽快让大家看到界面,filberRoot上会同时创立一个未初始化的Fiber对象,也就是uninitialFiber对象(rootFiber对象),这里的rootFiber指的就是咱们要挂载的结点,例如#root dom节点,在 React 利用中 FiberRoot 只有一个,而 rootFiber 能够有多个,因为 render 办法是能够调用屡次的,这里有一个相互指向的关系。

    1. 在 fiberRoot 对象中有一个 current 属性,存储 rootFiber
    2. 在 rootFiber 对象中有一个 stateNode 属性,指向 fiberRoot
  4. 其实每次React更新都是要比照新旧的Fiber,首次渲染的时候也是要比照新旧Fiber的,然而又因为首次渲染时基本没有上一次的Fiber,所以React才会在一开始就本人创立出一个未初始化也就是啥状态都没有uninitialFiber来伪装有上一次的状态,之后才会为本次渲染真正创立一个属于首次渲染的RootFiber,之后用这个RootFiber和方才那uninitialFiber作比照,到这儿为止都是React本人干的事儿,和咱们用React的人传进来的参数一点关系都没有。
  5. 当Root和uninitialFiber以及RootFiber都创立好了,才会真正开始首次的渲染调度,开始渲染后,会从ReactDOM.render传进去的第一个组件(组组件被转换成了树形构造的vnode)开始循环调度,为根组件下以及根组件等每一个节点不论是原生dom节点,还是函数组件或是类组件甚至React外部提供的组件都创立一个本人对应的fiber对象,在这个过程中所创立的fiber,叫做workInProgress,每个workInProgress都连贯着一个保留着以后节点更新前状态的fiber,这个前一个状态的fiber叫做current,不过因为是首次渲染,所以只有RootFiber有current也就是uninitialFiber,剩下的所有节点的current都是null(因为初始渲染 那个uninitialFiber对象是个未初始化的fiber对象,是空的)。

未完待续