前言
在下之前的技术栈是 Vue(虽然已经用了快一年的 React 了, Vue 也只是停留在 Vue2 的年代), 但为了更好地“生存”,以及团队发展的需要,将自顶向下学习 React, 并通过文章的形式巩固自己的学习成果。
设计理念
什么是 React?
官方说法是, React 是用 JavaScript 构建 快速响应 的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。
那 Vue 呢?
官方说法是, Vue 是一套用于构建用户界面的渐进式框架。
React 的设计理念是什么?
从官方给出的定义来看,React 的设计理念是构建一个能 快速响应 的框架,而制约快速响应主要有两种因素: CPU (js 的计算) 与 IO (网络延迟)
而 React 采用 异步可中断的更新机制 来达到快速响应的目的。
如何实现异步可中断的更新机制
旧的 React 架构 (React15 之前)
协调器通过 diff 算法判断组件是否需要更新,通知到渲染器进行渲染更新,在这个过程中,协调器与渲染器依次交替执行,同步更新。
这套 React 架构无法采用异步可断的更新机制,即不再符合 React 建立快速响应的初衷,因此,在 React16对这套架构进行了重构。
新的 React 架构 (React16 以后)
为了践行 快速响应 的理念,React16 新增了 Scheduler(调度器),通过 调度器 来管理异步更新的优先级,高优先级的更新会更快的被调度。而调度器和协调器是在内存中实现的,并不会展示到用户层,因此用户是无法察觉到的。
如何实现
Fiber 架构
什么是 Fiber ?
一、从架构的角度上看
每个Fiber节点有个对应的React element,多个 Fiber节点 通过 child、sibling、return 连接形成树
二、从数据结构的角度上看
每个 Fiber 对应一个组件,作为一个数据结构,它保存了组件相关的信息
// Fiber对应组件的类型 Function/Class/Host...
this.tag = tag;
// key属性
this.key = key;
// 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹
this.elementType = null;
// 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName
this.type = null;
// Fiber对应的真实DOM节点
this.stateNode = null;
三、作为动态的工作单元
例如 Fiber 上带有 Effect 的属性都是和副作用相关的
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// 作为静态数据结构的属性
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// 用于连接其他Fiber节点形成Fiber树
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
// 作为动态的工作单元的属性
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
// 调度优先级相关
this.lanes = NoLanes;
this.childLanes = NoLanes;
// 指向该fiber在另一次更新时对应的fiber
this.alternate = null;
}
React 是如何通过 Fiber 更新 DOM 呢?
在提及更新 DOM 之前,我们先说一下什么叫双缓存技术,例如在更新页面时,如果我们先对页面进行卸载,在挂载新的页面,如果卸载、挂载时间过长,可能导致页面白屏。因此我们可以通过双缓存技术,先将需要挂载的页面进行虚拟渲染,将渲染后的结果存放在内存中,再进行页面的替换。
因此在 React 中,其渲染更新也是采用了双缓存的技术,即在卸载旧 Fiber 树(current Fiber 树)之前,会先在内存中构建新的 Fiber 树(workInProgress Fiber树, 这里也会通过 diff 算法进行 Fiber 节点的复用),当新 Fiber 树渲染完成后,通过fiberRootNode的current指针指向workInProgress Fiber树使其变为current Fiber 树。
JSX
什么是 JSX
JSX 类似于 Vue 的 template 模版, Vue 会将 template 模版编译成 render 函数,而 React 会将 JSX 编译成 React.createElement 函数
JSX 与 Component 有啥关系呢?
如下图,我们知道 JSX 编译执行后的元素(React Element)的 type 属性,就是对应我们 Component
JSX 与 Fiber 关系
JSX是一种描述当前组件内容的数据结构,他不包含组件schedule、reconcile、render所需的相关信息,而 Fiber 节点保存了调度优先级相关、组件的 state、以及是否被渲染过的信息。
所以,在组件mount时,Reconciler根据JSX描述的组件内容生成组件对应的Fiber节点。
在update时,Reconciler将JSX与Fiber节点保存的数据对比,生成组件对应的Fiber节点,并根据对比结果为Fiber节点打上标记。
随便一提:刚开始一直很疑惑为啥是新的 JSX 和旧的 Fiber阶段保存的数据进行对比,而不是两颗 Fiber 树作对比,原因如下:
1 用 新的JSX 对比 旧Fiber 进行复用,可以减少生成新的 Fiber 节点步骤。
2 如果已经有新的 Fiber 树,那就没有必要对比了,直接用新的 Fiber 树就完事了