基础概念
Element对象与Fiber对象
Element对象与Fiber对象
Element 对象
定义
React 的 Element 对象是一个描述用户界面(UI)的普通 JavaScript 对象,通常由 React.createElement
或 JSX 语法生成。
作用
- 它是 React 应用中的一种描述 UI 的不可变数据结构。
- 表示一个虚拟 DOM 节点,是 React 处理 UI 的起点。
结构
一个典型的 React Element 对象结构如下:
const element = {
type: 'div', // 节点类型(字符串或组件类型)
props: { // 属性
children: [ // 子元素
{ type: 'span', props: { children: 'Hello' } },
{ type: 'button', props: { children: 'Click me' } }
]
},
key: null, // 用于唯一标识同一层级的节点
ref: null // 引用
};
特点
- 不可变性:一旦创建就不能修改。如果需要更新界面,React 会创建新的 Element 对象。
- 轻量级:它仅包含描述界面的必要信息。
Fiber 对象
定义
Fiber 是 React 16 引入的一种数据结构,用于描述组件的更新单元,它可以被认为是 React 的一种“工作单元”(unit of work)。
作用
- 它是 React 中调度和协调的核心,用于追踪组件的更新状态。
- 用于支持 React 的可中断渲染特性(Time Slicing 和 Concurrent Mode)。
结构
一个 Fiber 对象包含了非常多的属性,用于追踪组件的状态和更新过程。常见结构如下:
const fiber = {
tag: 5, // 节点类型(HostComponent、ClassComponent 等)
key: 'unique-key', // 唯一标识
type: 'div', // 节点的类型,与 Element 的 type 类似
stateNode: domElement, // 对应的 DOM 节点(如果是原生元素)
return: parentFiber, // 父 Fiber 节点
child: firstChildFiber, // 子 Fiber 节点
sibling: nextSiblingFiber, // 兄弟 Fiber 节点
alternate: oldFiber, // 双缓冲机制中的旧 Fiber
memoizedProps: currentProps,// 上一次渲染的 props
memoizedState: currentState,// 上一次渲染的 state
updateQueue: updates, // 更新队列
};
特点
- 动态可变:Fiber 对象会随着更新而变化,并在内存中保留更新的状态。
- 连接性强:每个 Fiber 都有指向父节点、子节点和兄弟节点的指针。
- 性能优化:Fiber 提供了双缓冲机制,通过
current
和alternate
双链表,优化了渲染和更新的效率。
Element 对象与 Fiber 对象的关系
-
创建:
- React 在渲染时,根据 JSX 或
React.createElement
创建 Element 对象。 - React 的协调(Reconciliation)过程会将 Element 转换成 Fiber 对象。
- React 在渲染时,根据 JSX 或
-
作用范围:
- Element 描述的是组件树的静态结构。
- Fiber 描述的是组件树的动态工作单元,包含状态、更新和副作用等信息。
-
更新机制:
- 当状态或属性变化时,React 会生成新的 Element 对象。
- Fiber 会根据新的 Element 对象,通过 Diff 算法对比并生成新的 Fiber 树,最终将变化反映到 DOM。
总结
特性 | Element 对象 | Fiber 对象 |
---|---|---|
定义 | UI 描述的不可变对象 | 用于描述组件树状态和调度的工作单元 |
生成方式 | React.createElement 或 JSX | 由协调过程生成 |
是否可变 | 不可变 | 可变 |
用途 | 描述界面结构 | 追踪组件状态和更新 |
层级 | 描述静态的 React 虚拟 DOM 树 | 处理动态的 Fiber 工作树 |
Fiber 是 React 内部实现的一部分,开发者通常无需直接操作,而是通过 React 的声明式 API(如 JSX 和 hooks)间接影响 Fiber 树的构建和更新。
FiberRootNode与HostRootFiber
在 React 中,FiberRootNode 和 HostRootFiber 是 React 内部实现的两个核心概念。它们在 React 的渲染和更新过程中扮演不同的角色,但彼此紧密关联。
1. FiberRootNode
定义
- FiberRootNode 是 React 应用的根节点,用于管理整个 React 应用的渲染状态。
- 它是一个表示应用根部的对象,通常对应于一个 React 渲染器(如 DOM 渲染器)的根容器(如
document.getElementById('root')
)。
作用
- 持有与 React 渲染相关的全局状态。
- 是 React 树的入口,包含指向根 Fiber 节点的引用(即 HostRootFiber)。
- 维护 React 树的更新调度。
结构
以下是 FiberRootNode 的核心属性:
const FiberRootNode = {
containerInfo: domContainer, // DOM 容器,比如 <div id="root">
current: HostRootFiber, // 指向当前 Fiber 树的根 Fiber 节点
pendingLanes: 0, // 所有等待调度的更新任务
finishedWork: null, // 已完成的 Fiber 树
callbackNode: null, // 当前调度的任务
callbackPriority: NoPriority, // 当前任务的优先级
eventTimes: [], // 记录各任务的触发时间
};
特点
- 全局管理:FiberRootNode 是 React 应用的全局入口,它是整个 React 渲染逻辑的起点。
- 连接 Fiber 树:
current
属性指向 HostRootFiber,它是连接到 Fiber 树的桥梁。 - 任务调度:包含任务优先级(
pendingLanes
、callbackNode
)和状态管理信息。
2. HostRootFiber
定义
- HostRootFiber 是 Fiber 树的根节点(Root Fiber),直接挂载在 FiberRootNode 的
current
属性上。 - 它是 Fiber 树中的第一个 Fiber 节点,对应整个应用的根组件。
作用
- 表示 React 应用的顶级组件,是 React 渲染和协调过程的起点。
- 持有整个应用的状态信息(如 Props、State 和更新队列)。
结构
HostRootFiber 是 Fiber 对象的一个实例,拥有以下关键属性:
const HostRootFiber = {
tag: 3, // 表示节点类型为 HostRoot
stateNode: FiberRootNode, // 指向 FiberRootNode,形成双向关联
memoizedState: { // 应用的状态
element: AppElement // 应用根组件生成的 React 元素
},
updateQueue: { // 更新队列,存储状态的变化
shared: {
pending: null // 指向等待处理的更新
}
},
child: AppFiber, // 指向应用根组件的 Fiber 节点
return: null, // 根节点没有父节点
};
特点
- Fiber 树的起点:它是整个 Fiber 树的根节点,代表 React 应用的根组件。
- 双向关联:
stateNode
指向 FiberRootNode,而 FiberRootNode 的current
又指回 HostRootFiber。 - 应用状态管理:负责存储应用的顶级状态和更新逻辑。
3. FiberRootNode 与 HostRootFiber 的关系
-
从属关系:
- FiberRootNode 是全局应用的根节点,持有全局的状态和调度信息。
- HostRootFiber 是 Fiber 树的根节点,代表应用的根组件,并存储应用状态。
- FiberRootNode 的
current
属性指向 HostRootFiber,形成直接关联。
-
渲染过程:
- React 初始化时会创建 FiberRootNode 和 HostRootFiber。
- FiberRootNode 的
containerInfo
是实际的 DOM 容器(如#root
),而 HostRootFiber 会逐步构建子 Fiber 节点,最终对应真实的 DOM 节点。
-
更新过程:
- 更新从 FiberRootNode 的调度开始。
- FiberRootNode 会触发调度,并通过
current
指向的 HostRootFiber 开始协调和渲染更新。
4. 关系示意图
FiberRootNode
├── containerInfo → <div id="root"> (DOM 容器)
├── current → HostRootFiber (根 Fiber 节点)
├── child → AppFiber (应用根组件的 Fiber)
├── child → ComponentFiber (组件的子节点)
5. 总结
特性 | FiberRootNode | HostRootFiber |
---|---|---|
定义 | 应用的根节点,管理全局状态和调度 | Fiber 树的根节点,代表应用的根组件 |
主要作用 | 维护全局状态、任务调度、连接 Fiber 树 | 存储应用状态、更新逻辑和子节点 |
关联性 | current 属性指向 HostRootFiber | stateNode 指向 FiberRootNode |
具体场景 | 对应 React 应用的 DOM 容器 | 对应 React 应用的顶级组件 |
FiberRootNode 是全局调度的核心,而 HostRootFiber 是组件树的根节点。两者紧密配合,共同完成 React 的渲染和更新流程。
WorkInProgress双缓存机制
React双缓存
React 的 WorkInProgress 双缓存机制 是 React Fiber 架构中优化渲染性能的核心设计之一。这种机制通过维护两棵 Fiber 树(current 树 和 workInProgress 树),以实现高效的更新与回溯。
1. 什么是双缓存机制?
双缓存机制的核心是:
- 两棵 Fiber 树:React 在内存中同时维护两棵 Fiber 树:
- current 树:表示当前已经渲染并展示在屏幕上的 UI 对应的 Fiber 树。
- workInProgress 树:用于计算下一次更新的 Fiber 树,它是
current 树
的备份。
- 当更新完成后,React 会将
workInProgress 树
替换为current 树
。
这种设计让 React 可以:
- 保证更新过程是安全的,即不会直接修改当前树,从而避免 UI 崩溃。
- 实现高效的增量更新,只更新变化的部分。
2. 双缓存机制的核心流程
2.1 树的创建和切换
-
初次渲染:
- React 创建一个 Fiber 树,称为
current 树
。 - 在初次渲染中,
workInProgress 树
是通过克隆current 树
创建的。
- React 创建一个 Fiber 树,称为
-
更新过程:
- 当触发更新时(如 state 或 props 变化),React 会基于
current 树
构建一个workInProgress 树
。 - React 在
workInProgress 树
中执行更新逻辑,并计算新状态。
- 当触发更新时(如 state 或 props 变化),React 会基于
-
提交阶段:
- 更新完成后,React 会将
workInProgress 树
替换为新的current 树
,并将计算的 UI 更新同步到 DOM。
- 更新完成后,React 会将
2.2 数据结构的切换
每个 Fiber 节点都有一个 alternate
属性,用于连接 current
树和 workInProgress
树。
这种双向指针的设计允许 React 在两棵树之间快速切换。
const fiber = {
tag: 5,
alternate: workInProgressFiber, // 指向对应的 workInProgress 节点
};
- current 树中的 Fiber 节点通过
alternate
指向对应的workInProgress 树
节点。 - workInProgress 树中的 Fiber 节点通过
alternate
指向对应的current 树
节点。
2.3 优化更新
- 如果某个节点未发生变化(
props
或state
未变化),React 会复用current 树
中对应的节点,而不是重新创建 Fiber 节点。 - 这种机制显著减少了内存分配和垃圾回收的开销,提高了性能。
3. 双缓存机制的具体实现
3.1 创建 WorkInProgress 树
React 的 createWorkInProgress
函数用于生成 workInProgress 树
。其核心逻辑如下:
- 检查
current 树
的 Fiber 节点是否已有alternate
(即对应的workInProgress
节点)。- 如果存在,直接复用并更新属性。
- 如果不存在,创建一个新的 Fiber 节点并与
current
节点建立双向连接。
function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
// 没有对应的 workInProgress,创建新节点
workInProgress = {
...current,
alternate: current,
};
current.alternate = workInProgress;
}
// 更新节点的 props 和其他状态
workInProgress.pendingProps = pendingProps;
return workInProgress;
}
3.2 提交阶段
在完成所有更新后,React 会将 workInProgress 树
替换为 current 树
。具体操作包括:
- 将
workInProgress 树
中的所有变更应用到真实的 DOM。 - 将
workInProgress 树
指定为新的current 树
。
4. 双缓存机制的优势
4.1 安全性
由于 React 在 workInProgress 树
中执行更新,而非直接修改 current 树
,即使更新中断或失败,current 树
仍然保持不变,用户不会看到不完整的 UI。
4.2 性能优化
双缓存机制避免了每次更新都重新构建整个 Fiber 树。通过复用未变化的节点,React 显著减少了内存分配和回收。
4.3 支持并发渲染
React 的双缓存机制为其支持 并发渲染(Concurrent Mode) 提供了基础:
- React 可以在
workInProgress 树
中进行异步更新,而不会阻塞主线程。 - 如果高优先级任务插入(如用户输入事件),React 可以随时中断
workInProgress 树
的更新,而无需担心 UI 崩溃。
5. 双缓存机制的缺点
- 内存开销:
- 双缓存需要在内存中同时维护两棵 Fiber 树(
current
和workInProgress
),内存占用较高。
- 双缓存需要在内存中同时维护两棵 Fiber 树(
- 实现复杂性:
- 双缓存的实现需要维护大量的指针和更新逻辑,代码复杂度较高。
6. 总结
React 的双缓存机制通过维护 current 树
和 workInProgress 树
,实现了以下功能:
- 提高渲染和更新的安全性。
- 提供性能优化(复用未变化的节点)。
- 支持并发渲染,响应更流畅的用户交互。
双缓存机制虽然在实现上较为复杂,但它是 React 高效更新和可中断渲染的重要基础,也是 React Fiber 架构的核心设计之一。
更新、渲染与提交
render阶段(beginWork&completeWork)->commit阶段(commitMutationEffect)
在 React 中,更新、渲染 和 提交 是 React Fiber 架构的核心阶段,构成了 React 应用从状态变化到用户界面更新的完整流程。这些阶段在 协调(Reconciliation) 和 渲染 的基础上分工明确,各自承担不同的任务。
1. 阶段划分
React 的更新过程可以分为以下三个阶段:
-
更新(Update)阶段
- 触发更新任务并进行调度。
- 包括 state、props 或 context 的变化触发更新,生成更新队列并计算优先级。
-
渲染(Render)阶段
- 也称为“协调阶段”。
- 构建或更新 Fiber 树(
workInProgress 树
),进行 Diff 比较,标记需要更新的部分。 - 该阶段是可中断的。
-
提交(Commit)阶段
- 应用 Fiber 树的变更到真实 DOM。
- 执行副作用(如 DOM 更新、生命周期方法调用、ref 更新等)。
- 该阶段是不可中断的。
2. 详细流程
2.1 更新(Update)阶段
定义
React 的更新阶段负责触发调度,包括响应用户交互、定时器、事件等。
关键步骤
-
触发更新:
- 更新由状态(
setState
)、属性(props 变化)、上下文(context 变化)或强制更新(forceUpdate
)触发。 - 每次更新会生成一个“更新任务”(
Update
),并加入到更新队列中。
- 更新由状态(
-
任务调度:
- React 会将任务交给调度器(Scheduler),根据任务的优先级(如同步任务、用户交互任务、低优先级任务等)安排执行。
- 高优先级任务(如用户输入)会打断低优先级任务。
-
进入渲染阶段:
- 调度完成后,进入渲染阶段,构建
workInProgress 树
。
- 调度完成后,进入渲染阶段,构建
2.2 渲染(Render)阶段
定义
渲染阶段是 React 构建或更新 Fiber 树的过程,决定哪些部分需要更新。这一阶段也被称为“协调阶段”。
特点
- 这一阶段是纯函数式的,React 不会直接操作 DOM。
- 可中断,支持优先级调度和时间切片(Time Slicing)。
关键步骤
-
构建
workInProgress 树
:- React 基于
current 树
创建或更新workInProgress 树
。 - 如果某个节点未发生变化(
props
或state
未变化),React 会复用节点。
- React 基于
-
Diff 算法:
- React 使用 Diff 算法比较
current 树
和workInProgress 树
,标记需要更新的 Fiber 节点。
- React 使用 Diff 算法比较
-
生成更新计划:
- 标记的 Fiber 节点会包含“更新标记”(flags),如:
Placement
:需要插入的新节点。Update
:需要更新的节点。Deletion
:需要删除的节点。
- 标记的 Fiber 节点会包含“更新标记”(flags),如:
-
渲染结束:
- 渲染阶段完成后,
workInProgress 树
将包含最新的更新信息。 - 进入提交阶段。
- 渲染阶段完成后,
2.3 提交(Commit)阶段
定义
提交阶段将 workInProgress 树
中的变更应用到真实的 DOM 上,并执行副作用。
特点
- 这一阶段是同步且不可中断的。
- 所有更新都将在提交阶段被应用到屏幕上。
关键步骤
-
Before Mutation 阶段:
- 调用生命周期方法(如
getSnapshotBeforeUpdate
)。 - 处理 refs(在 DOM 变更前)。
- 调用生命周期方法(如
-
Mutation 阶段:
- 将
workInProgress 树
的变更应用到 DOM。 - 包括节点插入、更新、删除等操作。
- 将
-
Layout 阶段:
- 调用生命周期方法(如
componentDidMount
、componentDidUpdate
)。 - 处理布局和副作用(如
useLayoutEffect
)。
- 调用生命周期方法(如
3. 渲染与提交的对比
特性 | 渲染阶段 | 提交阶段 |
---|---|---|
目的 | 构建更新计划,标记需要更新的节点 | 将变更应用到 DOM,并执行副作用 |
可中断性 | 可中断 | 不可中断 |
操作范围 | 虚拟 DOM,workInProgress 树 | 真实 DOM |
主要过程 | 构建 Fiber 树、Diff 比较 | 更新 DOM、触发副作用 |
4. 生命周期与 Hooks 的关联
React 的生命周期方法和 Hooks 在不同阶段被调用:
渲染阶段
- class 组件:
render
shouldComponentUpdate
- 函数组件:
useMemo
、useState
、useReducer
提交阶段
- class 组件:
componentDidMount
componentDidUpdate
getSnapshotBeforeUpdate
componentWillUnmount
- 函数组件:
useEffect
useLayoutEffect
5. 时间切片与并发模式
在 React 的并发模式中,渲染阶段变得更加灵活:
- 时间切片:渲染阶段可以分片执行,每一帧只占用主线程的一部分时间,从而避免阻塞用户交互。
- 优先级调度:高优先级任务(如用户输入)可以打断低优先级任务(如渲染更新)。
但提交阶段仍然是不可中断的,保证 UI 的一致性。
6. 总结
阶段 | 主要任务 | 特点 |
---|---|---|
更新阶段 | 接收更新请求,生成更新队列,并调度更新 | 任务调度,确定优先级 |
渲染阶段 | 构建 Fiber 树,Diff 比较并生成更新计划 | 可中断,操作虚拟 DOM |
提交阶段 | 应用变更到 DOM,执行副作用(如生命周期、Effect) | 不可中断,操作真实 DOM |
React 的三阶段模型(更新、渲染、提交)清晰地分离了调度逻辑、UI 计算和 DOM 操作,结合 Fiber 架构和时间切片机制,使得 React 能够高效地响应用户交互,同时保持良好的性能表现和一致性。
第一次挂载流程详解
React第一次挂载流程详解
Render阶段
可大致归为beginWork(递)和completeWork(归)两个阶段
1.beginWork流程(递)
- 建立节点的父子以及兄弟节点关联关系
child return sibling
属性- 给fiber节点打上
flag标记
(当前节点的flag)
渲染阶段结束
,fiberRootNode.finishwork=wip
,进入就断除
!
2.completeWork流程(归)
主要执行任务:
1.创建真实dom节点
,但是仍在内存中,未渲染到页面
2.处理flag与subtreeFlags
(标记
子树标识,用“|”
位运算处理)
3.建立真实DOM关系
,将子元素插入父元素中
completeWork归工作完成,将建立fiberRootNode.finishWork=wip
关系,当然进入
Commit阶段
commit工作前又会断掉此关系。(状态机,标识运行状态)
- 当commitMutationEffect(
commit执行完
),将dom渲染到页面中之后
root.current=finishedWork断开和老节点的联系
,指向新节点