最近学习了 react 源码,发现社区外面的大部分概念跟 react 18 的源码实现有些差别,比方 effect list、requestIdleCallback 等很多概念在新版本(React 18)中曾经逐步移除,理念也产生了变更,所以特此做个演绎总结,不便大家学习,有问题能够随时留言或者私信!(感兴趣的同学也欢送一起学习)
外围流程如下图所示
React 是以后最风行的前端框架,react 当中有几个外围的概念:渲染器、和谐器和调度器
渲染器
React 最后只是服务于 DOM,然而这之后被改编成也能同时反对原生平台的 React Native。因而,在 React 外部机制中引入了“渲染器”这个概念。
渲染器用于治理一棵 React 树,使其依据底层平台进行不同的调用。
渲染器同样位于 packages/
目录下:
- React DOM Renderer 将 React 组件渲染成 DOM。它实现了全局
ReactDOM
API,这在 npm 上作为react-dom
包。这也能够作为独自浏览器版本应用,称为react-dom.js
,导出一个ReactDOM
的全局对象. - React Native Renderer 将 React 组件渲染为 Native 视图。此渲染器在 React Native 外部应用。
- React Test Renderer 将 React 组件渲染为 JSON 树。这用于 Jest 的快照测试个性。在 npm 上作为 react-test-renderer 包公布。
另外一个官网反对的渲染器的是 react-art
。它已经是一个独立的 GitHub 仓库,然而当初咱们将此退出了主源代码树。
和谐器
即使 React DOM 和 React Native 渲染器的区别很大,但也须要共享一些逻辑。特地是协调算法须要尽可能类似,这样能够让申明式渲染,自定义组件,state,生命周期办法和 refs 等个性,放弃跨平台工作统一。
为了解决这个问题,不同的渲染器彼此共享一些代码。咱们称 React 的这一部分为“reconciler”。当解决相似于 setState()
这样的更新时,reconciler 会调用树中组件上的 render()
,而后决定是否进行挂载,更新或是卸载操作。
Reconciler 没有独自的包,因为他们临时没有公共 API。相同,它们被如 React DOM 和 React Native 的渲染器排除在外。
Stack reconciler
“stack”reconciler 是 React 15 及更早的解决方案。
stack reconciler 采纳递归的形式创立虚构 DOM 并提交 Dom Mutation,整个过程同步并且无奈中断工作或将其拆分为块。如果组件树的层级很深,递归会占用线程很多工夫,递归更新工夫超过了 16ms,用户交互就会卡顿。
Fiber reconciler
“fiber”reconciler 是一个新尝试,致力于解决 stack reconciler 中固有的问题,同时解决一些历史遗留问题。
Fiber 从 React 16 开始变成了默认的 reconciler。
它的次要指标是:
- 可能把可中断的工作切片解决。
- 可能调整优先级,重置并复用工作。
- 可能在父元素与子元素之间交织解决,以反对 React 中的布局。
- 可能在
render()
中返回多个元素。 - 更好地反对谬误边界。
非 并发模式进行以下操作:
顶部是个 slider,拖放后会怼整个 chart 区域缩放
火焰图调用信息如下
并发模式 进行同样的操作:
火焰图调用信息如下
通过比照,能够很显著的感触到并发模式下的流畅性
调度器
调度器次要蕴含两块:工夫调度、优先级调度
浏览器每一帧须要执行的工作
工夫调度
上面是 react 源码中的几种调度形式,有 先后 关系
形式一:isInputPending
参考文档:https://wicg.github.io/is-inp…
在运行须要显示某些内容的脚本时,开发人员明天须要做出判断。
如果脚本可能计算很长时间能力运行并且用户在产生这种状况时进行了某种输出,那么浏览器将须要等到脚本 实现后 能力分派输出事件。这会造成在响应输出事件之前有很长的提早,用户体验并不是很好,因而开发人员通常会将长脚本工作 合成 成更小的块,以容许用户代理在块之间调度事件。每次脚本执行时,它都须要以某种形式公布一条音讯,调用 requestAnimation 帧和 requestIdleCallback 的组合,或者采纳其它形式来生成能够被调度的事件,之后它能够在闲暇时再次被调用。即便在最好的状况下,脚本每次产生时也可能须要很多毫秒能力再次运行。所以可怜的是,这也不是一个很好的用户体验,因为局部初始的脚本被提早了很久才执行,只管他须要这么久的工夫。
为了防止这种取舍,Facebook
在 Chromium
中提出并实现了 isInputPending() API
,它能够进步网页的 响应能力,然而不会对性能造成太大影响。
isInputPending api 的指标是它当初将容许开发人员打消这种衡量。不再齐全屈服于用户代理,并且在屈从后必须承当一个或多个事件循环的老本,长时间运行的脚本当初能够运行到实现,同时依然放弃响应。
目前 isInputPending API 仅在 Chromium 的 87 版本开始提供,其余浏览器并未实现。
形式二:setImmediate
形式一次要应用在 Chromium 引擎的浏览器中,形式二从设计上,优先思考了 IE 的兼容性
该办法用来把一些须要长时间运行的操作放在一个回调函数里,在浏览器实现前面的其余语句后,就立即执行这个回调函数。
仅 IE 反对
形式三:MessageChannel
Channel Messaging API 的MessageChannel
接口容许咱们创立一个新的音讯通道,并通过它的两个MessagePort
属性发送数据。
在以下示例中,您能够看到应用 MessageChannel 构造函数实例化了一个 channel 对象。当 iframe 加载结束,咱们应用 MessagePort.postMessage 办法把一条音讯和 MessageChannel.port2 传递给 iframe。handleMessage 处理程序将会从 iframe 中(应用 MessagePort.onmessage 监听事件)接管到信息,将数据其放入 innerHTML 中。
var channel = new MessageChannel();
var para = document.querySelector('p');
var ifr = document.querySelector('iframe');
var otherWindow = ifr.contentWindow;
ifr.addEventListener("load", iframeLoaded, false);
function iframeLoaded() {otherWindow.postMessage('Hello from the main page!', '*', [channel.port2]);
}
channel.port1.onmessage = handleMessage;
function handleMessage(e) {para.innerHTML = e.data;}
形式四:setTimeout
给大家都懂,略过
调度器的切片工夫
切片间隔时间是 5ms,最大间隔时间是 300ms,源码如下
// Scheduler periodically yields in case there is other work on the main
// thread, like user events. By default, it yields multiple times per frame.
// It does not attempt to align with frame boundaries, since most tasks don't
// need to be frame aligned; for those that do, use requestAnimationFrame.
let yieldInterval = 5;
let deadline = 0;
// TODO: Make this configurable
// TODO: Adjust this based on priority?
const maxYieldInterval = 300;
let needsPaint = false;
切片和 React 互相关系
工作拆分
将和谐阶段(Reconciler)递归遍历 VDOM 这个大工作分成若干小工作,每个工作只负责 一个节点 的解决。
- workLoopSync or workLoopConcurrent
- performUnitOfWork
工作挂起、复原、终止
在新 workInProgress tree 的创立过程中,会同 currentFiber 的对应节点进行 Diff 比拟,生成对应的 falgs, 同时也会 复用 和 currentFiber 对应的节点对象,缩小新创建对象带来的开销。也就是说 无论是创立还是更新、挂起、复原以及终止操作都是产生在 workInProgress tree 创立过程中的。workInProgress tree 构建过程其实就是循环的执行工作和创立下一个工作。
挂起
当第一个小工作实现后,先判断这一帧是否还有 闲暇工夫 ,没有就挂起下一个工作的执行, 记住 以后挂起的节点,让出控制权给浏览器执行更高优先级的工作。
复原
在浏览器渲染完一帧后,判断以后帧是否有 剩余时间,如果有就复原执行之前挂起的工作。如果没有工作须要解决,代表和谐阶段实现,能够开始进入渲染阶段。
终止
其实并不是每次更新都会走到提交阶段。当在和谐过程中触发了新的更新,在执行下一个工作的时候,判断 是否有优先级更高的执行工作,如果有就终止原来将要执行的工作,开始新的 workInProgressFiber 树构建过程,开始新的更新流程。这样能够防止反复更新操作
工作优先级
下列是源码中提供的工作优先级,除了无优先工作外,其它工作数值越小优先级越高
// 无优先级工作
export const NoPriority = 0;
// 立刻执行工作
export const ImmediatePriority = 1;
// 用户阻塞工作
export const UserBlockingPriority = 2;
// 失常工作
export const NormalPriority = 3;
// 低优先级工作
export const LowPriority = 4;
// 闲暇执行工作
export const IdlePriority = 5;