共计 2857 个字符,预计需要花费 8 分钟才能阅读完成。
在本文之前说了 react15 的毛病,这里来说下 16 版本是怎么修复的。
我之前的一篇文章 写过 mini-react-fiber 版本, 那是一个简易版。
咱们这里剖析先做总结,而后依据总结的流程来看源码,这里次要就是串通主流程。
大架构:
首先从 react 源码层面执行,宏观角度来看,它实际上分为了两局部。
- render 阶段:次要就是用来生成新的 fiber 树并 diff 出有变动的节点。
- commit 阶段:获取到 render 阶段中 diff 进去的产生了变动的节点的 fiber,通过原生 api 更新页面。
也就是说,在 render 阶段中,只会做一些简单的运算,并不会真正的操作页面(在内存中做 新旧 fiber 对象比对,找出更新的 fiber 节点,或者是首次加载时 生成组装 html 片段 ),这一阶段是能够被打断的( 初始渲染时不会被打断,因为要让用户尽快看到界面 ),也就是说在 react 里的工夫分片的概念,分的就是简单运算的局部也就是这里,在这里这个 render 阶段也就是说可能会被高优先级的工作( 例如界面事件)打断。
直到 commit 阶段才会去通过 js 的原生 api 去批改 dom,commit 阶段是不能够被打断的,因为 commit 阶段是渲染阶段,它如果也是能够被打断的话,不一次性更新进去的话,就会呈现 相似网速过慢时 图片 迟缓加载的成果,和咱们的要求不符,所以 commit 阶段是同步的。
本质上我原来那篇 mini-react-fiber 文章也是遵循了 react 的两大阶段。
小架构:
当初咱们来认真说下 react 外部的架构划分:
react 外部架构理论能够分成三层:
- 调度层 Scheduler:调度工作的优先级,高优工作优先进入协调器
- 协调层 Reconciler:构建 Fiber 数据结构,比对 Fiber 对象找出差别, 记录 Fiber 对象要进行的 DOM 操作(初始加载的时候,负责组装 html 片段)
- 渲染层 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 阶段,这个阶段渲染器 工作是不能够被打断的,它负责渲染更新界面。
而后咱们晓得了这个过程之后,大体来捋一下加载 和更新的流程,后续再贴代码 具体的地位。
加载
- 首先是 react 中有一个函数 createElement 创立生成 虚构 dom,后续会依据这个 vNode 来生成对应的 fiber 构造。
- 进入流程 咱们调用 render 办法来进行首次渲染,首先 react 外部有一个 reactRoot 对象,而后 root 对象外部外部有一个_internalRoot 外面存储的 fiberRoot, 这个 reactRoot 对象是整个利用的根,每次 react 开始调度,不论是初始渲染还是 setState 都是从根开始的。
首次渲染会已最高级优先级来执行,因为要尽快让大家看到界面,filberRoot 上会同时创立一个未初始化的 Fiber 对象,也就是 uninitialFiber 对象(rootFiber 对象),这里的 rootFiber 指的就是咱们要挂载的结点,例如 #root dom 节点,在 React 利用中 FiberRoot 只有一个,而 rootFiber 能够有多个,因为 render 办法是能够调用屡次的,这里有一个相互指向的关系。
- 在 fiberRoot 对象中有一个 current 属性,存储 rootFiber
- 在 rootFiber 对象中有一个 stateNode 属性,指向 fiberRoot
- 其实每次 React 更新都是要比照新旧的 Fiber,首次渲染的时候也是要比照新旧 Fiber 的, 然而又因为首次渲染时基本没有上一次的 Fiber,所以 React 才会在一开始就本人创立出一个未初始化也就是啥状态都没有 uninitialFiber 来伪装有上一次的状态,之后才会为本次渲染真正创立一个属于首次渲染的 RootFiber,之后用这个 RootFiber 和方才那 uninitialFiber 作比照,到这儿为止都是 React 本人干的事儿,和咱们用 React 的人传进来的参数一点关系都没有。
- 当 Root 和 uninitialFiber 以及 RootFiber 都创立好了,才会真正开始首次的渲染调度,开始渲染后,会从 ReactDOM.render 传进去的第一个组件(组组件被转换成了树形构造的 vnode)开始循环调度,为根组件下以及根组件等每一个节点不论是原生 dom 节点,还是函数组件或是类组件甚至 React 外部提供的组件都创立一个本人对应的 fiber 对象,在这个过程中所创立的 fiber,叫做 workInProgress,每个 workInProgress 都连贯着一个保留着以后节点更新前状态的 fiber,这个前一个状态的 fiber 叫做 current,不过因为是首次渲染,所以只有 RootFiber 有 current 也就是 uninitialFiber,剩下的所有节点的 current 都是 null(因为初始渲染 那个 uninitialFiber 对象是个未初始化的 fiber 对象,是空的)。
未完待续