React 是当前最受欢迎的前端框架之一,其以简洁的组件化思想和强大的性能优化机制,广泛应用于现代 Web 开发中。想要深入理解React的设计原理,学习源码是最好的方式。
createElement、jsx与jsxDev
在 React 中,createElement、jsx 和 jsxDEV 都是创建 React 元素的函数,它们之间的主要区别在于它们的用途和使用环境。每个函数在不同的场景下有特定的作用,通常由编译器(如 Babel)生成,以适应开发和生产环境。
以下面的 jsx 代码为例:
const element = <h1>Hello, world!</h1>;
经过编译后会变成:
const element = React.createElement('h1', null, 'Hello, world!');
React.createElement 参数
- type:元素类型(如
div、h1,或是自定义组件) - props:元素的属性对象,包含传递给元素的所有属性(如
id、className等) - children:子元素,作为后续参数传递
React.createElement 会返回一个 React 元素对象,如下所示:
{
type: 'h1',
props: {
children: 'Hello, world!'
},
key: null,
ref: null,
_owner: null,
_store: {}
}
jsx 函数是在 React 17 之后引入的,它用于简化 JSX 的创建流程。相比于 createElement,它更加高效,因为它可以直接创建元素对象,而不需要在内部做复杂的校验。这也是 React 17 之后推荐的用于生产环境的方式。
示例代码:
function App() {
return <h1>Hello World</h1>;
}
编译后的代码:
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello world' });
}
而jsxDEV 是专门为开发环境设计的函数,提供了额外的调试信息(如文件名、行号和列号)。当发生错误时,这些信息可以帮助开发者快速定位问题。
React Fiber架构
React Fiber 是 React 16 引入的一种新的协调机制,将同步递归无法中断的更新,重构为异步的可中断更新,本质上是为了解决React的性能问题。
Fiber是React中的核心数据结构,Fiber包含三层含义:
- 作为架构来说,之前
React15的Reconciler采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler。React16的Reconciler基于Fiber节点实现,被称为Fiber Reconciler。 - 作为静态的数据结构来说,每个
Fiber节点对应一个React element,保存了该组件的类型(函数组件/类组件/原生组件等)、对应的DOM节点等信息。 - 作为动态的工作单元来说,每个
Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新等)。
Fiber结点的属性如下:
export class FiberNode {
type: any;
tag: WorkTag;
pendingProps: Props;
key: Key;
stateNode: any;
ref: Ref;
return: FiberNode | null;
sibling: FiberNode | null;
child: FiberNode | null;
index: number;
memoizedProps: Props | null;
memoizedState: any;
alternate: FiberNode | null;
flags: Flags;
subtreeFlags: Flags;
updateQueue: unknown;
deletions: FiberNode[] | null;
constructor(tag: WorkTag, pendingProps: Props, key: Key) {
// 实例
this.tag = tag;
this.key = key;
this.stateNode = null;
this.type = null;
// 构成树状结构
this.return = null;
this.sibling = null;
this.child = null;
this.index = 0;
this.ref = null;
// 作为工作单元
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.memoizedState = null;
this.updateQueue = null;
this.alternate = null;
// 副作用
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
}
}
FiberNode 类可以根据功能分为几个关键类别:
1. 结构相关
-
树结构:
return,sibling,child用于构建 Fiber 树,表示父节点、兄弟节点和子节点。index标识节点在兄弟节点中的位置。
-
标识和引用:
key用于唯一标识节点,ref用于获取组件或 DOM 元素的引用。
2. 类型和实例
-
类型和实例:
type表示节点的类型(如组件类、函数组件、DOM 元素)。stateNode是节点对应的实例(如组件实例或 DOM 元素)。
3. 属性和状态
-
属性和状态管理:
pendingProps是当前更新的属性。memoizedProps和memoizedState存储上次渲染的属性和状态,用于比较和决定是否需要更新。updateQueue管理与节点相关的更新。
4. 双缓存和替代
-
双缓存机制:
alternate指向当前节点的替代节点,支持 React 的双缓存更新策略。
5. 副作用和更新
-
副作用管理:
flags和subtreeFlags用于标记节点及其子树的副作用(如更新、删除)。deletions存储需要删除的子节点列表。
React更新流程
React架构可以分为下面三个部分:
Scheduler(调度器):负责管理任务的优先级和调度,决定何时执行更新任务Reconciler(协调器):在 Scheduler 的指导下执行更新计算,确定哪些组件需要更新并生成 Fiber 树Renderer(渲染器):将 Reconciler 生成的 Fiber 树转换为实际的 DOM 更新,执行具体的渲染操作
当组件的状态或属性发生变化时,首先由 Scheduler 负责管理任务的优先级,通过时间切片和中断机制确保高优先级任务的快速响应。接着,Reconciler 生成新的 Fiber 树,利用 Diff 算法高效地比较新旧节点,标记需要更新的节点,并收集副作用。最后,Renderer 在提交阶段执行真实的 DOM 更新,将变化应用到用户界面,并处理在 Reconciler 中收集的副作用,确保 UI 的一致性和准确性。这一流程的协作使得 React 能够高效地应对复杂应用的渲染需求,优化性能和用户体验。React更新流程如下图所示:
通过这种分工,React 可以在复杂应用中高效地管理和执行更新,确保用户界面的快速响应和一致性。调度器确保了任务的合理安排,协调器优化了更新计算,而渲染器则高效地执行了实际的 DOM 操作。