React源码解读(一)
相关资料
-
补充 Babel 编译 jsx Babel · The compiler for next generation JavaScript
-
Min Heap 可视化 www.cs.usfca.edu/~galles/vis…
-
关于 monorepo pnpm.io/workspaces
相关问题
React 中, "setState"是同步还是异步?
在 React 18 之前,setState 的行为取决于调用的上下文环境。而在 React 18 引入自动批处理(Automatic Batching)后,行为更加统一。
React 18 之前的行为:
- 在 React 事件处理函数中:异步批量更新
- 在 setTimeout、Promise、原生事件等场景中:同步更新
React 18 的行为:
在所有场景下都默认进行批量更新(异步),无论是在事件处理函数、setTimeout 还是 Promise 中。
// React 18 之前的行为
class Example extends React.Component {
state = { count: 0 };
handleClick = () => {
// 异步批量更新
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 输出 0,而不是 1
};
handleTimeout = () => {
setTimeout(() => {
// React 18 之前:同步更新
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // React 18 之前输出 1
}, 0);
};
}
// React 18 的行为
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
// 批量更新
setCount(c => c + 1);
setCount(c => c + 1);
// 只会触发一次重新渲染
};
const handleAsync = () => {
setTimeout(() => {
// React 18:也会批量更新
setCount(c => c + 1);
setCount(c => c + 1);
// 只触发一次重新渲染
}, 0);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>同步点击</button>
<button onClick={handleAsync}>异步点击</button>
</div>
);
}
批处理流程:
graph TD
A["触发 setState"] --> B{"是否在批处理上下文"}
B -->|是| C["将更新加入队列"]
B -->|否 React 18前| D["立即执行更新"]
B -->|否 React 18| C
C --> E["等待当前执行栈清空"]
E --> F["合并所有更新"]
F --> G["执行一次重新渲染"]
D --> G
如何强制同步更新(React 18):
import { flushSync } from 'react-dom';
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
flushSync(() => {
setCount(c => c + 1);
});
// 这里 count 已经更新
console.log(count); // 输出最新值
};
return <button onClick={handleClick}>强制同步</button>;
}
React 中key的作用是什么?
key 是 React 用于识别哪些元素发生了变化、添加或删除的特殊属性。它在 Diff 算法中起着关键作用,帮助 React 优化渲染性能。
key 的核心作用:
- 唯一标识元素:在同一层级的兄弟节点中唯一标识每个元素
- 优化 Diff 算法:帮助 React 快速定位变化的节点
- 保持组件状态:key 不变时,组件实例会被复用,状态得以保留
- 触发重新挂载:key 变化时,旧组件卸载,新组件挂载
// 不使用 key 的问题示例
function BadList() {
const [items, setItems] = useState(['A', 'B', 'C']);
const addItem = () => {
setItems(['X', ...items]); // 在开头插入新元素
};
return (
<div>
{/* 没有 key,React 会认为第一个元素从 A 变成了 X,第二个从 B 变成了 A... */}
{items.map(item => (
<input defaultValue={item} />
))}
<button onClick={addItem}>添加</button>
</div>
);
}
// 正确使用 key
function GoodList() {
const [items, setItems] = useState([
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 3, name: 'C' }
]);
const addItem = () => {
setItems([{ id: Date.now(), name: 'X' }, ...items]);
};
return (
<div>
{/* 使用 key,React 能正确识别新插入的元素 */}
{items.map(item => (
<input key={item.id} defaultValue={item.name} />
))}
<button onClick={addItem}>添加</button>
</div>
);
}
Diff 算法中 key 的作用流程:
graph LR
A["开始 Diff"] --> B{"是否有 key"}
B -->|有 key| C["通过 key 查找旧节点"]
B -->|无 key| D["按位置顺序比较"]
C --> E{"找到相同 key"}
E -->|是| F["复用节点,更新属性"]
E -->|否| G["创建新节点"]
D --> H["逐个比较类型"]
H --> I{"类型相同"}
I -->|是| J["复用节点"]
I -->|否| K["删除旧节点,创建新节点"]
key 的最佳实践:
// ❌ 不要使用索引作为 key(除非列表永远不会重新排序)
items.map((item, index) => <div key={index}>{item}</div>);
// ✅ 使用稳定的唯一标识
items.map(item => <div key={item.id}>{item.name}</div>);
// ✅ 使用 key 强制重新挂载组件
function UserProfile({ userId }) {
// 当 userId 变化时,整个组件重新挂载,状态重置
return (
<div key={userId}>
<UserForm userId={userId} />
</div>
);
}
// ✅ 列表过滤后保持 key 稳定
function FilteredList({ items, filter }) {
const filtered = items.filter(item => item.category === filter);
return (
<ul>
{filtered.map(item => (
// 使用原始数据的 id,而不是过滤后的索引
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
key 对性能的影响对比:
// 场景:在列表开头插入新元素
// 没有 key:React 会更新所有元素
// [A, B, C] -> [X, A, B, C]
// 操作:更新 4 个元素(X←A, A←B, B←C, C←新建)
// 有 key:React 只需插入新元素
// [{id:1, A}, {id:2, B}, {id:3, C}] -> [{id:4, X}, {id:1, A}, {id:2, B}, {id:3, C}]
// 操作:插入 1 个新元素(X),其他复用
React 工程架构
React 采用 Monorepo 架构管理多个包,核心包之间职责分明、相互协作。整体架构遵循"分层解耦"的设计原则。
graph TB
A["React 应用层"] --> B["react 核心包"]
B --> C["react-reconciler 协调器"]
C --> D["scheduler 调度器"]
C --> E["react-dom 渲染器"]
C --> F["react-native 渲染器"]
C --> G["react-noop-renderer 测试渲染器"]
style B fill:#61dafb
style C fill:#ffd700
style D fill:#ff6b6b
style E fill:#51cf66
react
react 包是 React 的核心 API 层,提供了开发者直接使用的组件定义、Hooks 等基础能力,但不包含具体的渲染和调度逻辑。
核心职责:
- 定义组件基础类(Component、PureComponent)
- 提供 Hooks API(useState、useEffect 等)
- 创建 React 元素(createElement、JSX)
- 提供 Context API
核心 API 示例:
// 1. 组件定义
import { Component, PureComponent } from 'react';
class MyComponent extends Component {
render() {
return <div>{this.props.children}</div>;
}
}
// 2. Hooks API
import { useState, useEffect, useMemo, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// 3. createElement(JSX 编译后的实际调用)
import { createElement } from 'react';
// JSX: <div className="container">Hello</div>
// 编译后:
const element = createElement(
'div',
{ className: 'container' },
'Hello'
);
// 4. Context API
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Button</button>;
}
react 包的架构定位:
react 包只负责定义 API 接口和数据结构,不关心如何调度、如何渲染。这种设计使得 React 可以支持多种渲染目标(DOM、Native、Canvas 等)。
react-dom
react-dom 是 React 的 DOM 渲染器,负责将 React 元素渲染到浏览器 DOM 中,并处理 DOM 事件系统。
核心职责:
- 将虚拟 DOM 渲染到真实 DOM
- 管理 DOM 事件系统(事件委托、合成事件)
- 提供 Hydration(服务端渲染的客户端激活)
- 提供 Portals(跨层级渲染)
核心 API 示例:
// 1. 渲染到 DOM (React 18+)
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// 2. 服务端渲染的 Hydration
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);
// 3. Portals - 将子节点渲染到不同的 DOM 节点
import { createPortal } from 'react-dom';
function Modal({ children }) {
return createPortal(
<div className="modal">{children}</div>,
document.getElementById('modal-root')
);
}
// 4. flushSync - 强制同步更新
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// DOM 已同步更新
}
事件系统架构:
sequenceDiagram
participant User as 用户操作
participant DOM as DOM 节点
participant Root as 根节点监听器
participant Synthetic as 合成事件系统
participant Handler as React 事件处理器
User->>DOM: 点击按钮
DOM->>Root: 事件冒泡到根节点
Root->>Synthetic: 捕获原生事件
Synthetic->>Synthetic: 创建合成事件对象
Synthetic->>Handler: 分发到对应组件
Handler->>Handler: 执行 onClick 等处理器
react-reconciler
react-reconciler 是 React 的协调器(核心引擎),负责管理整个更新流程,包括 Fiber 架构、Diff 算法、调度协调等。
核心职责:
- 构建和管理 Fiber 树
- 执行 Diff 算法(协调过程)
- 管理组件生命周期
- 协调调度器(Scheduler)和渲染器(Renderer)
Fiber 架构:
// Fiber 节点的简化结构
const fiber = {
// 节点类型和标识
tag: WorkTag, // 标识 Fiber 类型(函数组件、类组件等)
key: string | null, // 用于 Diff 的 key
elementType: any, // React 元素类型
type: any, // 组件类型(函数、类等)
// Fiber 树结构
return: Fiber | null, // 父 Fiber
child: Fiber | null, // 第一个子 Fiber
sibling: Fiber | null, // 下一个兄弟 Fiber
index: number, // 在父节点中的索引
// 数据和状态
pendingProps: any, // 新的 props
memoizedProps: any, // 上一次的 props
memoizedState: any, // 上一次的 state
updateQueue: UpdateQueue, // 更新队列
// 副作用
flags: Flags, // 副作用标记(增删改等)
subtreeFlags: Flags, // 子树副作用标记
deletions: Array<Fiber>, // 需要删除的子节点
// 双缓存机制
alternate: Fiber | null, // 指向另一棵树的对应节点
};
双缓存机制:
graph LR
A["current Fiber 树<br/>当前显示"] --> B["workInProgress Fiber 树<br/>正在构建"]
B --> C["完成构建"]
C --> D["切换指针"]
D --> E["workInProgress 成为 current"]
E --> A
style A fill:#51cf66
style B fill:#ffd700
style E fill:#51cf66
协调过程(Reconciliation):
// 简化的协调流程示例
function workLoop(deadline) {
// 循环处理 Fiber 节点
while (workInProgress !== null && !shouldYield(deadline)) {
workInProgress = performUnitOfWork(workInProgress);
}
// 如果还有工作未完成,继续调度
if (workInProgress !== null) {
requestIdleCallback(workLoop);
} else {
// 工作完成,提交更新
commitRoot();
}
}
function performUnitOfWork(fiber) {
// 1. beginWork: 处理当前 Fiber
const next = beginWork(fiber);
if (next !== null) {
// 有子节点,返回子节点
return next;
}
// 2. completeWork: 没有子节点,完成当前节点
let completedWork = fiber;
while (completedWork !== null) {
completeUnitOfWork(completedWork);
// 如果有兄弟节点,返回兄弟节点
if (completedWork.sibling !== null) {
return completedWork.sibling;
}
// 回到父节点
completedWork = completedWork.return;
}
return null;
}
scheduler
scheduler 是 React 的调度器,负责任务的优先级调度和时间切片,实现可中断的异步渲染。
核心职责:
- 任务优先级管理
- 时间切片(Time Slicing)
- 任务调度和执行
- 使用最小堆管理任务队列
优先级系统:
// React 的优先级级别(从高到低)
const ImmediatePriority = 1; // 立即执行(如用户输入)
const UserBlockingPriority = 2; // 用户交互(如点击、滚动)
const NormalPriority = 3; // 普通优先级(如数据请求)
const LowPriority = 4; // 低优先级(如分析上报)
const IdlePriority = 5; // 空闲时执行(如预加载)
// 调度任务示例
import {
unstable_scheduleCallback as scheduleCallback,
unstable_NormalPriority as NormalPriority,
unstable_UserBlockingPriority as UserBlockingPriority
} from 'scheduler';
// 高优先级任务(用户交互)
scheduleCallback(UserBlockingPriority, () => {
updateUserInput();
});
// 普通优先级任务(数据更新)
scheduleCallback(NormalPriority, () => {
fetchAndUpdateData();
});
时间切片机制:
gantt
title 时间切片渲染流程
dateFormat X
axisFormat %L
section 浏览器帧
执行 JS: 0, 5
渲染: 5, 8
空闲: 8, 16
section 下一帧
执行 JS: 16, 21
渲染: 21, 24
空闲: 24, 32
section React 任务
任务片段1: 0, 5
任务片段2: 16, 21
任务片段3: 32, 37
调度器工作流程:
// 简化的调度器实现
class Scheduler {
constructor() {
this.taskQueue = new MinHeap(); // 最小堆存储任务
this.isScheduling = false;
}
// 调度任务
scheduleTask(priority, callback) {
const task = {
id: taskIdCounter++,
callback,
priorityLevel: priority,
expirationTime: getCurrentTime() + priorityToTimeout(priority),
};
this.taskQueue.push(task);
if (!this.isScheduling) {
this.isScheduling = true;
this.requestHostCallback(this.flushWork);
}
}
// 执行任务
flushWork(deadline) {
while (this.taskQueue.peek() && !shouldYieldToHost(deadline)) {
const task = this.taskQueue.pop();
const callback = task.callback;
const continuationCallback = callback();
// 如果任务返回了继续执行的函数,重新加入队列
if (typeof continuationCallback === 'function') {
task.callback = continuationCallback;
this.taskQueue.push(task);
}
}
// 还有任务未完成,继续调度
if (this.taskQueue.peek()) {
return true;
}
this.isScheduling = false;
return false;
}
}
// 判断是否需要让出控制权
function shouldYieldToHost(deadline) {
const timeElapsed = getCurrentTime() - deadline.startTime;
return timeElapsed >= 5; // 5ms 时间片
}
Lane 模型与调度链路
Lane 是 React 18 中引入的优先级抽象,使用位掩码对任务进行分类。一次更新可以占用多个 Lane,而根节点会记录所有子树传播上来的 childLanes,从而在顶层快速检索出最高优先级的待办任务。
function ensureRootIsScheduled(root) {
const existingCallback = root.callbackNode;
const nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
if (nextLanes === NoLanes) {
// 没有工作需要调度,取消现有回调
if (existingCallback !== null) {
cancelCallback(existingCallback);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const newCallbackNode = scheduleCallback(
lanePriorityToSchedulerPriority(newCallbackPriority),
performConcurrentWorkOnRoot.bind(null, root)
);
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
graph TD
A["组件触发更新<br/>scheduleUpdateOnFiber"] --> B["合并 lane<br/>markRootUpdated"]
B --> C["getNextLanes"]
C --> D["ensureRootIsScheduled"]
D --> E["scheduleCallback"]
E --> F["performConcurrentWorkOnRoot"]
F --> G["workLoopConcurrent"]
G --> H["是否需要让出控制权?"]
H -->|是| I["shouldYieldToHost"]
H -->|否| J["renderRootConcurrent"]
自动批处理与批量提交
React 18 默认开启自动批处理:只要处于同一个事件循环 tick 内触发的更新,都会在 Render 阶段合并,再在 Commit 阶段一次性提交。这依赖 flushSyncCallbacks 与 batchedUpdates 对调度入口的包装。
function dispatchSetState( fiber, queue, action ) {
const lane = requestUpdateLane(fiber);
const update = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: null
};
enqueueConcurrentHookUpdate(fiber, queue, update, lane);
scheduleUpdateOnFiber(fiber, lane);
}
// 在事件系统中:
function batchedUpdates(fn, a) {
const prevIsBatching = isBatchingUpdates;
isBatchingUpdates = true;
try {
return fn(a);
} finally {
isBatchingUpdates = prevIsBatching;
if (!isBatchingUpdates) {
flushSyncCallbacks();
}
}
}
并发特性与 Transition API
Concurrency 模式允许 React 在渲染过程中"可打断",为用户交互保留快速响应的时间窗口。startTransition 与 useTransition 会将更新标记为 TransitionLane,让调度器在用户交互与过渡动画之间做优先级调度。
import { startTransition, useTransition } from 'react';
function SearchBox() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const [list, setList] = useState([]);
const handleChange = (event) => {
const value = event.target.value;
setQuery(value); // SyncLane,高优先级,立即更新输入框
startTransition(() => { // TransitionLane,可被打断
const filtered = filterHeavyData(value);
setList(filtered);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending ? <Spinner /> : <ResultList data={list} />}
</div>
);
}
graph LR
UserInput["用户输入事件"] --> Immediate["同步更新: setQuery"]
UserInput --> Transition["startTransition"]
Transition --> Scheduler
Scheduler -->|高优先级| CommitSync["立即提交输入框"]
Scheduler -->|低优先级| RenderTransition["后台渲染过滤列表"]
RenderTransition --> CommitTransition
从并发渲染到提交
performConcurrentWorkOnRoot 会在时间片允许的情况下反复执行渲染任务;一旦渲染完成或任务超时,就会转入 commitRoot。在切换 fiber 树之前,React 会再次确认当前最高优先级是否仍然有效,必要时重新进入渲染循环。
function performConcurrentWorkOnRoot(root) {
const originalCallbackNode = root.callbackNode;
const lanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
if (lanes === NoLanes) {
return null;
}
const exitStatus = renderRootConcurrent(root, lanes);
if (exitStatus !== RootInProgress) {
const finishedWork = root.finishedWork;
commitRoot(root, finishedWork, lanes);
}
if (root.callbackNode === originalCallbackNode) {
return null;
}
return performConcurrentWorkOnRoot.bind(null, root);
}
react-noop-renderer
react-noop-renderer 是 React 的测试渲染器,不执行实际的渲染操作,主要用于测试 React 核心逻辑。
核心用途:
- 测试 React 协调算法
- 测试 Fiber 架构
- 测试调度逻辑
- 验证 React 行为而不依赖 DOM
// react-noop-renderer 使用示例(测试场景)
import ReactNoop from 'react-noop-renderer';
test('测试组件更新', () => {
function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
const root = ReactNoop.createRoot();
root.render(<Counter />);
// 验证渲染结果(不涉及真实 DOM)
expect(root).toMatchRenderedOutput(<div>0</div>);
// 触发更新
act(() => {
// 触发 state 更新
});
expect(root).toMatchRenderedOutput(<div>1</div>);
});
jsx 编译
JSX 是 JavaScript 的语法扩展,允许在 JavaScript 中编写类似 HTML 的标记。浏览器无法直接执行 JSX,需要通过编译器转换为标准的 JavaScript 代码。
编译器
React 的 JSX 编译主要由 Babel 完成。Babel 通过插件 @babel/plugin-transform-react-jsx 将 JSX 语法转换为 React.createElement() 调用(旧版转换)或 jsx() 函数调用(新版转换)。
编译器的核心任务:
- 词法分析:将 JSX 代码分解为 tokens
- 语法分析:构建抽象语法树(AST)
- 转换:将 JSX 节点转换为函数调用
- 代码生成:输出标准 JavaScript 代码
graph LR
A["JSX 源代码"] --> B["词法分析器<br/>Lexer"]
B --> C["Token 流"]
C --> D["语法分析器<br/>Parser"]
D --> E["AST 抽象语法树"]
E --> F["转换器<br/>Transformer"]
F --> G["新的 AST"]
G --> H["代码生成器<br/>Generator"]
H --> I["JavaScript 代码"]
style A fill:#61dafb
style I fill:#51cf66
Babel 插件系统:
// Babel 的 JSX 插件配置
{
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"runtime": "automatic", // 新版 JSX 转换
"development": false,
"importSource": "react"
}
]
]
}
编译配置与编译过程
React 17+ 引入了全新的 JSX 转换机制,不再需要在文件中显式导入 React。
两种 JSX 转换方式对比:
JSX 代码
// 原始 JSX 代码
function App() {
return (
<div className="container">
<h1>Hello World</h1>
<p>This is a paragraph</p>
</div>
);
}
Babel 的 JSX 转换
解析 JSX
Babel 首先将 JSX 代码解析为 AST(抽象语法树)结构:
// JSX 元素 <div className="container">Hello</div> 的 AST 表示(简化)
{
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
name: { type: 'JSXIdentifier', name: 'div' },
attributes: [
{
type: 'JSXAttribute',
name: { type: 'JSXIdentifier', name: 'className' },
value: { type: 'StringLiteral', value: 'container' }
}
]
},
children: [
{ type: 'StringLiteral', value: 'Hello' }
],
closingElement: {
type: 'JSXClosingElement',
name: { type: 'JSXIdentifier', name: 'div' }
}
}
转换 JSX 元素
旧版转换(Classic Runtime - React 16 及之前):
需要在文件顶部导入 React,JSX 会被转换为 React.createElement() 调用。
// 需要显式导入 React
import React from 'react';
function App() {
return React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Hello World'),
React.createElement('p', null, 'This is a paragraph')
);
}
新版转换(Automatic Runtime - React 17+):
不需要导入 React,自动从 react/jsx-runtime 导入 JSX 函数。
// 自动导入(由 Babel 添加,开发者不可见)
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
function App() {
return _jsxs('div', {
className: 'container',
children: [
_jsx('h1', { children: 'Hello World' }),
_jsx('p', { children: 'This is a paragraph' })
]
});
}
转换流程对比:
graph TB
subgraph "旧版转换 Classic"
A1["JSX 代码"] --> B1["检查 React 导入"]
B1 -->|没有导入| C1["报错"]
B1 -->|已导入| D1["转换为 React.createElement"]
D1 --> E1["输出代码"]
end
subgraph "新版转换 Automatic"
A2["JSX 代码"] --> B2["自动导入 jsx runtime"]
B2 --> D2["转换为 _jsx/_jsxs"]
D2 --> E2["输出代码"]
end
style A1 fill:#61dafb
style A2 fill:#61dafb
style E1 fill:#51cf66
style E2 fill:#51cf66
Babel 配置
完整的 Babel 配置示例:
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-react',
{
runtime: 'automatic', // 使用新版 JSX 转换
development: process.env.NODE_ENV === 'development',
importSource: 'react' // 从 react 包导入
}
],
[
'@babel/preset-env',
{
targets: {
browsers: ['last 2 versions', 'ie >= 11']
}
}
]
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-transform-runtime'
]
};
开发环境与生产环境的差异:
// 开发环境配置(包含调试信息)
{
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"runtime": "automatic",
"development": true, // 开启开发模式
"importSource": "react"
}
]
]
}
// 生产环境配置(优化体积)
{
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"runtime": "automatic",
"development": false, // 关闭开发模式
"importSource": "react"
}
]
]
}
示例代码
原始 JSX 文件 App.jsx
// App.jsx - 原始 JSX 文件
function App({ title, count }) {
const handleClick = () => {
console.log('Clicked!');
};
return (
<div className="app">
<h1>{title}</h1>
<div className="counter">
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
{count > 5 && <div className="warning">Count is high!</div>}
<ul>
{[1, 2, 3].map(item => (
<li key={item}>Item {item}</li>
))}
</ul>
</div>
);
}
export default App;
使用 Babel CLI 转换 JSX
# 安装 Babel 相关包
npm install --save-dev @babel/core @babel/cli @babel/preset-react
# 使用 Babel CLI 转换单个文件
npx babel App.jsx --out-file App.js --presets @babel/preset-react
# 转换整个目录
npx babel src --out-dir dist --presets @babel/preset-react
# 监听文件变化,自动转换
npx babel src --out-dir dist --watch --presets @babel/preset-react
转换后的文件 App.js
// App.js - 转换后的文件(新版 JSX 转换)
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from 'react/jsx-runtime';
function App({ title, count }) {
const handleClick = () => {
console.log('Clicked!');
};
return _jsxs('div', {
className: 'app',
children: [
_jsx('h1', { children: title }),
_jsxs('div', {
className: 'counter',
children: [
_jsxs('p', { children: ['Count: ', count] }),
_jsx('button', {
onClick: handleClick,
children: 'Increment'
})
]
}),
count > 5 && _jsx('div', {
className: 'warning',
children: 'Count is high!'
}),
_jsx('ul', {
children: [1, 2, 3].map(item =>
_jsxs('li', {
children: ['Item ', item]
}, item)
)
})
]
});
}
export default App;
关键转换细节:
// 1. 单个子元素使用 jsx()
<div>Hello</div>
// 转换为:
_jsx('div', { children: 'Hello' })
// 2. 多个子元素使用 jsxs()
<div>
<span>Hello</span>
<span>World</span>
</div>
// 转换为:
_jsxs('div', {
children: [
_jsx('span', { children: 'Hello' }),
_jsx('span', { children: 'World' })
]
})
// 3. key 属性特殊处理
<li key={item.id}>{item.name}</li>
// 转换为:
_jsx('li', { children: item.name }, item.id) // key 作为第三个参数
// 4. 条件渲染保持不变
{condition && <div>Show</div>}
// 转换为:
condition && _jsx('div', { children: 'Show' })
// 5. 循环渲染保持不变
{items.map(item => <li key={item}>{item}</li>)}
// 转换为:
items.map(item => _jsx('li', { children: item }, item))
React 核心流程
React 的核心流程包括**创建(Mount)和更新(Update)**两个阶段,分别对应组件的首次渲染和后续更新。这两个阶段都遵循 Render 阶段和 Commit 阶段的双阶段处理模式。
graph TB
A["应用启动"] --> B["创建流程 Mount"]
B --> C{"用户交互/状态变化"}
C --> D["更新流程 Update"]
D --> C
subgraph "创建流程"
B --> B1["创建 Fiber 树"]
B1 --> B2["Render 阶段"]
B2 --> B3["Commit 阶段"]
B3 --> B4["首次渲染完成"]
end
subgraph "更新流程"
D --> D1["触发更新"]
D1 --> D2["调度更新"]
D2 --> D3["Render 阶段 Diff"]
D3 --> D4["Commit 阶段"]
D4 --> D5["更新完成"]
end
style B fill:#61dafb
style D fill:#ffd700
创建
组件的创建(Mount)流程是指组件首次渲染到页面的整个过程,包括创建 Fiber 树、执行渲染逻辑、生成 DOM 并挂载到页面。
创建流程的核心步骤:
- 创建 FiberRoot 和 RootFiber
- Render 阶段:构建 Fiber 树
- Commit 阶段:将 Fiber 树渲染到 DOM
完整的创建流程:
sequenceDiagram
participant App as React 应用
participant Root as FiberRoot
participant Render as Render 阶段
participant Commit as Commit 阶段
participant DOM as 真实 DOM
App->>Root: createRoot(container)
Root->>Root: 创建 FiberRoot
Root->>Root: 创建 RootFiber
App->>Root: root.render(<App />)
Root->>Render: 进入 Render 阶段
Render->>Render: beginWork - 处理每个 Fiber
Render->>Render: 创建子 Fiber 节点
Render->>Render: 深度优先遍历
Render->>Render: completeWork - 完成 Fiber
Render->>Render: 构建完整 Fiber 树
Render->>Commit: 进入 Commit 阶段
Commit->>Commit: before mutation - 执行 getSnapshotBeforeUpdate
Commit->>DOM: mutation - 创建 DOM 节点并插入
Commit->>Commit: layout - 执行 useLayoutEffect/componentDidMount
Commit->>Commit: 异步执行 useEffect
Commit->>App: 渲染完成
代码示例 - 创建流程:
// 1. 创建 React 应用(React 18)
import { createRoot } from 'react-dom/client';
function App() {
return <div>Hello React</div>;
}
// 创建根节点
const container = document.getElementById('root');
const root = createRoot(container);
// 触发首次渲染
root.render(<App />);
// 2. 内部创建流程(简化)
function createRoot(container) {
// 创建 FiberRoot(整个应用的根)
const fiberRoot = {
containerInfo: container, // DOM 容器
current: null, // 指向 RootFiber
finishedWork: null, // 已完成的工作
};
// 创建 RootFiber(Fiber 树的根)
const rootFiber = createFiber(HostRoot, null, null);
// 相互引用
fiberRoot.current = rootFiber;
rootFiber.stateNode = fiberRoot;
return {
render(element) {
// 创建 Update 对象
const update = {
payload: { element }, // 要渲染的元素
};
// 将 Update 加入更新队列
enqueueUpdate(rootFiber, update);
// 调度渲染
scheduleUpdateOnFiber(rootFiber);
}
};
}
// 3. Render 阶段 - 构建 Fiber 树
function renderRootSync(root) {
let workInProgress = root.current.alternate;
if (workInProgress === null) {
// 首次渲染,创建 workInProgress 树
workInProgress = createWorkInProgress(root.current);
}
// 工作循环
workLoopSync(workInProgress);
// 标记完成的工作
root.finishedWork = workInProgress;
}
function workLoopSync(fiber) {
while (fiber !== null) {
fiber = performUnitOfWork(fiber);
}
}
function performUnitOfWork(fiber) {
// beginWork:处理当前 Fiber,创建子 Fiber
const next = beginWork(fiber);
if (next !== null) {
return next; // 返回子 Fiber
}
// 没有子节点,执行 completeWork
completeUnitOfWork(fiber);
// 返回下一个要处理的 Fiber(兄弟或父节点)
return getNextUnitOfWork(fiber);
}
// 4. Commit 阶段 - 提交到 DOM
function commitRoot(root) {
const finishedWork = root.finishedWork;
// 阶段 1: before mutation
commitBeforeMutationEffects(finishedWork);
// 阶段 2: mutation - 操作 DOM
commitMutationEffects(finishedWork);
// 切换 current 指针
root.current = finishedWork;
// 阶段 3: layout
commitLayoutEffects(finishedWork);
// 异步执行 effects
schedulePassiveEffects(finishedWork);
}
Fiber 树构建示例:
// JSX 结构
<App>
<Header>
<h1>Title</h1>
</Header>
<Content>
<p>Text</p>
</Content>
</App>
// 构建的 Fiber 树结构
const fiberTree = {
tag: FunctionComponent,
type: App,
child: { // App 的子节点
tag: FunctionComponent,
type: Header,
sibling: { // Header 的兄弟节点
tag: FunctionComponent,
type: Content,
child: {
tag: HostComponent,
type: 'p',
child: {
tag: HostText,
text: 'Text'
}
}
},
child: { // Header 的子节点
tag: HostComponent,
type: 'h1',
child: {
tag: HostText,
text: 'Title'
}
}
}
};
更新
组件的更新(Update)流程是指组件状态或属性变化后重新渲染的过程。与创建流程类似,更新流程也经历 Render 和 Commit 两个阶段,但会执行 Diff 算法来复用已有节点。
触发更新的方式:
// 1. useState/useReducer 触发更新
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // 触发更新
};
return <button onClick={handleClick}>{count}</button>;
}
// 2. 类组件 setState 触发更新
class Counter extends React.Component {
state = { count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1 }); // 触发更新
};
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>;
}
}
// 3. forceUpdate 强制更新
class MyComponent extends React.Component {
handleClick = () => {
this.forceUpdate(); // 跳过 shouldComponentUpdate
};
}
// 4. 根节点重新渲染
root.render(<App newProp="value" />); // 触发更新
更新流程:
graph TB
A["触发更新"] --> B["创建 Update 对象"]
B --> C["加入 UpdateQueue"]
C --> D["调度更新 scheduleUpdateOnFiber"]
D --> E{"判断优先级"}
E -->|高优先级| F["同步更新"]
E -->|普通优先级| G["异步调度"]
F --> H["Render 阶段"]
G --> H
H --> I["beginWork - Diff 算法"]
I --> J["复用或创建 Fiber"]
J --> K["completeWork"]
K --> L["标记副作用"]
L --> M["Commit 阶段"]
M --> N["执行 DOM 操作"]
N --> O["执行生命周期/Hooks"]
O --> P["更新完成"]
style A fill:#ffd700
style H fill:#ff6b6b
style M fill:#51cf66
更新流程核心代码:
// 1. 触发更新 - useState 的实现
function useState(initialState) {
const hook = {
memoizedState: initialState,
queue: { pending: null }
};
const dispatch = (action) => {
// 创建 Update
const update = {
action,
next: null
};
// 加入更新队列
enqueueUpdate(hook.queue, update);
// 调度更新
scheduleUpdateOnFiber(currentFiber);
};
return [hook.memoizedState, dispatch];
}
// 2. Render 阶段 - beginWork 执行 Diff
function beginWork(current, workInProgress) {
// current: 旧 Fiber
// workInProgress: 新 Fiber
if (current !== null) {
// 更新流程
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 比较 props 是否变化
if (oldProps === newProps && !hasContextChanged()) {
// props 没变化,可以复用
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
}
// 根据不同的 tag 处理
switch (workInProgress.tag) {
case FunctionComponent:
return updateFunctionComponent(current, workInProgress);
case ClassComponent:
return updateClassComponent(current, workInProgress);
case HostComponent:
return updateHostComponent(current, workInProgress);
default:
return null;
}
}
// 3. Diff 算法 - 协调子节点
function reconcileChildren(current, workInProgress, nextChildren) {
if (current === null) {
// 创建流程
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
} else {
// 更新流程 - 执行 Diff
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren
);
}
}
// Diff 算法的核心逻辑
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
// 处理单个元素
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(returnFiber, currentFirstChild, newChild)
);
}
}
// 处理数组(多个子元素)
if (Array.isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
// 处理文本节点
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(returnFiber, currentFirstChild, newChild)
);
}
// 删除旧节点
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
// 4. Commit 阶段 - 执行副作用
function commitMutationEffects(finishedWork) {
// 遍历 Fiber 树,执行副作用
commitMutationEffectsOnFiber(finishedWork);
}
function commitMutationEffectsOnFiber(fiber) {
const flags = fiber.flags;
// 处理 ref
if (flags & Ref) {
const current = fiber.alternate;
if (current !== null) {
commitDetachRef(current);
}
}
// 处理副作用
const primaryFlags = flags & (Placement | Update | Deletion);
switch (primaryFlags) {
case Placement: {
// 插入节点
commitPlacement(fiber);
fiber.flags &= ~Placement;
break;
}
case Update: {
// 更新节点
const current = fiber.alternate;
commitWork(current, fiber);
break;
}
case Deletion: {
// 删除节点
commitDeletion(fiber);
break;
}
}
// 递归处理子节点
if (fiber.child !== null) {
commitMutationEffectsOnFiber(fiber.child);
}
// 处理兄弟节点
if (fiber.sibling !== null) {
commitMutationEffectsOnFiber(fiber.sibling);
}
}
更新优化策略:
// 1. React.memo - 浅比较 props
const MemoizedComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});
// 2. useMemo - 缓存计算结果
function ExpensiveComponent({ data }) {
const result = useMemo(() => {
return expensiveCalculation(data);
}, [data]);
return <div>{result}</div>;
}
// 3. useCallback - 缓存函数引用
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Clicked');
}, []); // 依赖为空,函数不会重新创建
return <ChildComponent onClick={handleClick} />;
}
// 4. PureComponent - 自动浅比较
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}
// 5. shouldComponentUpdate - 自定义比较
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.value !== this.props.value;
}
render() {
return <div>{this.props.value}</div>;
}
}
小结:并发更新、自动批处理、错误边界与 Suspense 的协同,让 React 能够在保持 UI 响应性的前提下构建复杂的可恢复交互流程。
Hooks 内部机制
Hooks 让函数组件具备状态与副作用能力,其背后依赖于 Fiber 上的 Hook 链表、dispatcher 的动态切换以及副作用队列的统一调度。
Hook 链表与 Fiber 绑定
每个函数组件在 Render 阶段都会创建一条 Hook 链表,保存在当前 Fiber 的 memoizedState 属性上。链表遍历顺序与 Hooks 调用顺序严格一致,这也是"Hooks 不能放在条件语句内"的根本原因。
function renderWithHooks(current, workInProgress, Component, props) {
renderLanes = workInProgress.lanes;
currentlyRenderingFiber = workInProgress;
workInProgressHook = current !== null ? current.memoizedState : null;
ReactCurrentDispatcher.current = current === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props);
finishRenderingHooks();
return children;
}
graph LR
A["Function Component"] --> B["renderWithHooks"]
B --> C["HooksDispatcherOnMount"]
C --> D["mountState"]
D --> E["Hook 节点"]
E --> F["next Hook"]
Hook 节点结构(简化):
const hook = {
memoizedState: initialState, // 保存当前值
baseState: initialState,
baseQueue: null,
queue: {
pending: null, // 循环链表,存放更新
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState,
},
next: null, // 指向下一个 Hook
};
dispatcher 的挂载与切换
- 初次渲染:使用
HooksDispatcherOnMount,每个 Hook 会调用mountXxx分支,创建 Hook 节点并挂载更新队列。 - 更新阶段:切换为
HooksDispatcherOnUpdate,调用updateXxx分支,复用旧的 Hook 链表节点,并按顺序取出保存的状态。 - 非严格模式下的双渲染:React 在开发环境会多次执行函数组件,以检验 Hooks 的副作用是否幂等。
const HooksDispatcherOnMount = {
useState: mountState,
useReducer: mountReducer,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
// ...
};
const HooksDispatcherOnUpdate = {
useState: updateState,
useReducer: updateReducer,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
};
useState 与 useReducer 的调度链
useState 和 useReducer 共用同一套更新机制:
- 在 Hook queue 上创建循环链表节点。
- 调度
scheduleUpdateOnFiber。 - 在 Render 阶段合并所有 update(
processUpdateQueue)。
function mountState(initialState) {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
hook.memoizedState = initialState();
} else {
hook.memoizedState = initialState;
}
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: hook.memoizedState,
};
hook.queue = queue;
const dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
return [hook.memoizedState, dispatch];
}
useEffect 与 useLayoutEffect 的执行时序
两类副作用都会在 Render 阶段收集依赖,但执行时机不同:
useLayoutEffect:在 Commit 的 layout 阶段同步执行,适合操作 DOM、测量布局。useEffect:在 Commit 结束后异步执行,适合订阅、网络请求等不会阻塞渲染的副作用。
sequenceDiagram
participant Render as Render 阶段
participant Layout as layout 阶段
participant Passive as passive 阶段
Render->>Render: 收集 useEffect/useLayoutEffect
Render->>Layout: 注册 layoutEffects
Render->>Passive: 注册 passiveEffects
Layout->>Layout: 执行 useLayoutEffect / cleanup
Passive->>Passive: 执行 useEffect / cleanup
function commitLayoutEffects(root, finishedWork) {
// layoutEffectsList 是一个单向链表
while (nextEffect !== null) {
commitLayoutEffectOnFiber(finishedWork);
nextEffect = nextEffect.nextEffect;
}
}
function flushPassiveEffects() {
// passiveEffectsList 同样使用链表存储
while (pendingPassiveHookEffects.length > 0) {
const effect = pendingPassiveHookEffects.pop();
invokeGuardedCallback(effect.destroy);
invokeGuardedCallback(effect.create);
}
}
useTransition 与 useDeferredValue 的内部协作
这两个并发特性分别通过:
useTransition:返回[isPending, startTransition],底层创建新的TransitionLane,并在 Hook queue 中记录pendingLanes。useDeferredValue:在 Hook 内部调用useState与startTransition,将低优先级更新延后执行。
function updateTransition() {
const hook = updateWorkInProgressHook();
const pending = hook.memoizedState;
const queue = hook.queue;
// 如果有 Transition 尚未完成,则保持 isPending=true
const currentTransition = queue.pending;
hook.memoizedState = currentTransition;
return [pending !== null, queue.startTransition];
}
这些机制保证了函数组件在保持纯粹的同时,仍能表达状态管理、异步副作用与并发控制等丰富能力。
事件系统深入
React 在 react-dom 中实现了一套跨浏览器的合成事件系统,核心目标是统一事件模型、减少监听器数量并支持优先级调度。
合成事件分层架构
graph TD
Native["原生事件"] --> Listener["事件监听包装"]
Listener --> Plugin["事件插件系统"]
Plugin --> SyntheticEvent["合成事件对象"]
SyntheticEvent --> Dispatch["分发阶段"]
Dispatch --> UpdateQueue["更新任务调度"]
- 监听阶段:在根容器(
rootContainerInstance)上注册捕获与冒泡监听器,统一由事件系统处理。 - 插件阶段:通过
DOMEventProperties、SimpleEventPlugin等插件将原生事件映射为合成事件。 - 合成事件对象:
SyntheticEvent提供跨平台的接口,内部使用对象池减少 GC。 - 分发与调度:按照捕获→冒泡顺序执行监听函数,并为离散事件(如点击)和连续事件(如滚动)赋予不同 Scheduler 优先级。
function dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
rootContainerElement
) {
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue = [];
// 组装 dispatchQueue:捕获/冒泡阶段的 listener 与事件对象
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags
);
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
事件优先级与 Scheduler
React 会根据事件类型分配优先级,从而决定使用 DiscreteEventPriority、ContinuousEventPriority 等不同 lane:
const DiscreteEventPriority = SyncLane;
const ContinuousEventPriority = InputContinuousLane;
const DefaultEventPriority = DefaultLane;
const eventPriorityMap = {
click: DiscreteEventPriority,
keydown: DiscreteEventPriority,
mousemove: ContinuousEventPriority,
scroll: ContinuousEventPriority,
};
function getEventPriority(domEventName) {
return eventPriorityMap[domEventName] ?? DefaultEventPriority;
}
当事件触发时,React 会调用 requestEventTime、requestUpdateLane 为更新匹配相同优先级的 lane,使得高优先级交互能够在并发模式下优先处理。
Pointer 捕获与冒泡策略
React 针对 Pointer 事件提供了统一的捕获/冒泡处理:
- 通过
accumulateSinglePhaseListeners收集捕获链。 - 在
dispatchQueue中记录 listener 与阶段信息。 - 对支持 capture 的事件(如
pointerenter)在根节点执行模拟捕获。
sequenceDiagram
participant Root
participant Target
participant Scheduler
Root->>Root: 注册顶层监听
Target->>Root: 原生事件冒泡
Root->>Scheduler: 根据事件优先级调度
Scheduler->>Target: 执行捕获 listener
Scheduler->>Target: 执行冒泡 listener
事件系统与批处理
React 事件处理函数默认被 batchedUpdates 包裹,多个 setState 会在一次 Render 中合并,减少 DOM 重绘次数;只有当开发者显式调用 flushSync 时才会即时刷新。
服务端渲染与 Server Components
React 18 提供了 Streaming SSR 与 Server Components(RSC),让服务端与客户端共享同一套组件模型。
Streaming SSR 渲染流水线
flowchart TD
Start["renderToPipeableStream/ renderToReadableStream"]
Start --> Shell["生成 shell HTML"]
Shell --> Pipe["pipe() / ReadableStream pipe"]
Pipe --> Client["客户端接收并执行 hydrateRoot"]
Start --> Chunks["异步数据块 Suspense 边界"]
Chunks --> Pipe
核心 API:
import { renderToPipeableStream } from 'react-dom/server';
const stream = renderToPipeableStream(<App />, {
onShellReady() {
stream.pipe(res);
},
onAllReady() {
res.end();
}
});
- onShellReady:当 shell 内容准备好即可推送到客户端,实现"先展示骨架"体验。
- Suspense data chunk:遇到
Suspensefallback 时,服务端会输出占位 HTML,待数据准备好后再推送替换块。 - hydrateRoot:客户端接手后与现有 DOM 对比,复用而非重建节点。
Server Components 基础流程
RSC 将组件拆分为 Server/Client 两类,通过 react-server-dom-webpack 管理传输协议。
sequenceDiagram
participant Client
participant Server
participant Flight
Client->>Server: 请求 RSC payload
Server->>Flight: 渲染 Server Component
Flight-->>Client: 发送 JSON payload
Client->>Client: 解码为 React Element Tree
Client->>Client: client components hydrate
关键点:
- Server Component 只能使用服务端资源(DB、文件系统),不支持浏览器 API。
- 数据通过 Flight 协议序列化为 JSON 流,Client 根据
module_reference懒加载对应 Client Component。 useAPI 允许在 Server Component 中直接awaitPromise,使数据依赖与视图结构同步。
// Server Component
export default async function Page() {
const user = await fetchUser();
return (
<Layout>
<Sidebar user={user} />
<Content />
</Layout>
);
}
Hydration 与局部更新
hydrateRoot与renderRootConcurrent共用 Fiber 双缓存逻辑。- 当 Streaming chunk 抵达时,Client 会调用
scheduleTask拉起对应Suspensefiber 的重渲染。
构建体系与 Feature Flag
React 官方仓库使用 Rollup 构建多个目标产物,并通过 Feature Flag 精细控制功能开关。
Rollup 构建管线
graph LR
Entry["packages/react/index.js"] --> Rollup["Rollup 配置"]
Rollup --> Builds["不同 bundle"]
Builds -->|UMD| ReactUMD["react.development.js"]
Builds -->|CJS| ReactCJS["index.js"]
Builds -->|ESM| ReactESM["react.development.mjs"]
构建脚本位于 scripts/rollup/build.js:
const bundles = [
{
entry: 'packages/react/index.js',
global: 'React',
externals: ['react'],
bundleType: UMD_DEV,
},
// ...
];
function createBundle(bundle, bundleType, filename, globalName) {
return {
input: { entry: bundle.entry },
output: { file: filename, format: getFormat(bundleType), name: globalName },
plugins: getPlugins(bundleType),
};
}
bundleType控制输出格式(UMD/CJS/ESM)、开发/生产模式。if (__DEV__)等环境变量通过 Rollup 插件在构建时替换,提高 dead code elimination 效果。
Feature Flag 与入口选择
React 通过 shared/ReactFeatureFlags.* 定义不同环境下的特性组合:
export const enableProfilerTimer = __PROFILE__;
export const enableNewReconciler = false;
构建时会根据目标(www、fb-www、open-source)选择不同的 flag 文件,从而在不发布多份源码的情况下定制差异化行为。
开发体验:调试与 Profiling
- 使用
yarn build react/index,react-dom/index --type=NODE_DEV构建本地调试包。 --type=FB_DEV会启用 Facebook 内部专用 flag,方便对比差异。- Profiling 构建(
--profile)会启用enableProfilerTimer,用于分析 Scheduler 与渲染性能。
测试与调试工具链
React 维护多套渲染器与测试框架,确保核心算法在不同宿主环境下表现一致。
React Noop Renderer
react-noop-renderer 提供"无宿主"的渲染实现,常用于 Fiber 行为测试。
import ReactNoop from 'react-noop-renderer';
import { act } from 'react-dom/test-utils';
test('state updates', () => {
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
const root = ReactNoop.createRoot();
act(() => {
root.render(<Counter />);
});
expect(root).toMatchRenderedOutput(<button>0</button>);
});
react-test-renderer 与快照
react-test-renderer允许在 Node 环境渲染组件树并与快照对比。- 支持
actAPI,确保所有副作用在断言前执行。
import renderer, { act } from 'react-test-renderer';
it('renders correctly', () => {
let tree;
act(() => {
tree = renderer.create(<App />);
});
expect(tree.toJSON()).toMatchSnapshot();
});
DevTools 与 Fiber 调试
DevTools 通过 react-reconciler 的 __REACT_DEVTOOLS_GLOBAL_HOOK__ 注入,实现:
- Fiber 树可视化、props/state 浏览。
- Profiler Flamechart,基于调度时间段生成热点图。
- 记录网络边界(
Suspense)、State 变更历史。
graph LR
ReactApp --> Hook["__REACT_DEVTOOLS_GLOBAL_HOOK__"]
Hook --> Bridge["DevTools Bridge"]
Bridge --> Extension["Chrome/Standalone DevTools"]
End-to-End 测试
React 使用 jest + karma + 浏览器驱动执行 E2E 测试,确保在真实 DOM 环境下行为一致。
fixtures/目录包含多种宿主环境示例。packages/react-dom/src/events/__tests__覆盖事件系统的冒泡、捕获、阻止默认等场景。
数据结构与算法
React 源码中大量运用了各种数据结构和算法来优化性能和实现复杂功能。理解这些数据结构和算法是深入理解 React 源码的关键。
数据结构
堆
特性
堆是一种特殊的完全二叉树结构,分为最小堆和最大堆。React 的 Scheduler 使用最小堆来管理任务队列。
最小堆的核心特性:
- 父节点 ≤ 子节点:父节点的值总是小于或等于子节点
- 完全二叉树:除最后一层外,其他层都是满的
- 数组存储:可以用数组高效存储,无需指针
- 时间复杂度:
- 插入:O(log n)
- 删除最小值:O(log n)
- 查找最小值:O(1)
数组存储规则:
// 对于索引为 i 的节点
const parentIndex = Math.floor((i - 1) / 2); // 父节点索引
const leftChildIndex = 2 * i + 1; // 左子节点索引
const rightChildIndex = 2 * i + 2; // 右子节点索引
最小堆实现:
class MinHeap {
constructor() {
this.heap = [];
}
// 获取堆顶元素(最小值)
peek() {
return this.heap[0];
}
// 获取堆大小
size() {
return this.heap.length;
}
// 插入元素
push(node) {
// 1. 将元素添加到数组末尾
this.heap.push(node);
// 2. 上浮操作,维护堆的性质
this.siftUp(this.heap.length - 1);
}
// 删除并返回堆顶元素
pop() {
if (this.heap.length === 0) {
return null;
}
const first = this.heap[0];
const last = this.heap.pop();
if (this.heap.length > 0) {
// 将最后一个元素放到堆顶
this.heap[0] = last;
// 下沉操作,维护堆的性质
this.siftDown(0);
}
return first;
}
// 上浮操作
siftUp(index) {
const node = this.heap[index];
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2);
const parent = this.heap[parentIndex];
// 如果当前节点 >= 父节点,停止上浮
if (node.expirationTime >= parent.expirationTime) {
break;
}
// 交换当前节点和父节点
this.heap[index] = parent;
index = parentIndex;
}
this.heap[index] = node;
}
// 下沉操作
siftDown(index) {
const node = this.heap[index];
const length = this.heap.length;
const halfLength = length >>> 1; // 只需要遍历到有子节点的位置
while (index < halfLength) {
const leftIndex = 2 * index + 1;
const rightIndex = leftIndex + 1;
const left = this.heap[leftIndex];
const right = this.heap[rightIndex];
// 找出子节点中较小的一个
let minIndex = leftIndex;
let minChild = left;
if (rightIndex < length && right.expirationTime < left.expirationTime) {
minIndex = rightIndex;
minChild = right;
}
// 如果当前节点 <= 较小的子节点,停止下沉
if (node.expirationTime <= minChild.expirationTime) {
break;
}
// 交换当前节点和较小的子节点
this.heap[index] = minChild;
index = minIndex;
}
this.heap[index] = node;
}
}
堆的操作流程:
graph TB
subgraph "插入操作"
A1["添加到数组末尾"] --> A2["与父节点比较"]
A2 -->|小于父节点| A3["交换位置"]
A3 --> A2
A2 -->|大于等于父节点| A4["插入完成"]
end
subgraph "删除操作"
B1["保存堆顶元素"] --> B2["将最后元素移到堆顶"]
B2 --> B3["与子节点比较"]
B3 -->|大于较小子节点| B4["交换位置"]
B4 --> B3
B3 -->|小于等于子节点| B5["删除完成"]
end
style A4 fill:#51cf66
style B5 fill:#51cf66
React 中的应用
React Scheduler 使用最小堆管理任务队列,确保始终优先执行过期时间最早(优先级最高)的任务。
// Scheduler 中的任务队列实现
const taskQueue = []; // 最小堆存储待执行任务
const timerQueue = []; // 最小堆存储延迟任务
// 调度任务
function unstable_scheduleCallback(priorityLevel, callback, options) {
const currentTime = getCurrentTime();
let startTime;
if (typeof options === 'object' && options !== null) {
const delay = options.delay;
startTime = typeof delay === 'number'
? currentTime + delay
: currentTime;
} else {
startTime = currentTime;
}
// 根据优先级计算过期时间
let timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT; // -1,立即过期
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT; // 250ms
break;
case NormalPriority:
timeout = NORMAL_PRIORITY_TIMEOUT; // 5000ms
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT; // 10000ms
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT; // 最大整数
break;
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
}
const expirationTime = startTime + timeout;
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (startTime > currentTime) {
// 延迟任务,加入 timerQueue
newTask.sortIndex = startTime;
push(timerQueue, newTask);
if (peek(taskQueue) === null && peek(timerQueue) === newTask) {
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// 立即执行的任务,加入 taskQueue
newTask.sortIndex = expirationTime;
push(taskQueue, newTask); // 使用堆的 push 操作
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}
// 执行任务队列
function flushWork(hasTimeRemaining, initialTime) {
isHostCallbackScheduled = false;
isPerformingWork = true;
try {
return workLoop(hasTimeRemaining, initialTime);
} finally {
currentTask = null;
isPerformingWork = false;
}
}
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
currentTask = peek(taskQueue); // 获取堆顶任务(优先级最高)
while (currentTask !== null) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// 任务未过期且需要让出控制权
break;
}
const callback = currentTask.callback;
if (typeof callback === 'function') {
currentTask.callback = null;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
const continuationCallback = callback(didUserCallbackTimeout);
if (typeof continuationCallback === 'function') {
// 任务未完成,继续执行
currentTask.callback = continuationCallback;
} else {
// 任务完成,从堆中移除
if (currentTask === peek(taskQueue)) {
pop(taskQueue); // 使用堆的 pop 操作
}
}
} else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
return currentTask !== null;
}
优先级队列示意图:
graph TD
A["任务队列<br/>最小堆"] --> B["任务1: 过期时间 100ms<br/>UserBlockingPriority"]
A --> C["任务2: 过期时间 5000ms<br/>NormalPriority"]
A --> D["任务3: 过期时间 10000ms<br/>LowPriority"]
B --> E["任务4: 过期时间 250ms"]
B --> F["任务5: 过期时间 300ms"]
style B fill:#ff6b6b
style C fill:#ffd700
style D fill:#51cf66
链表
基本使用
链表是一种线性数据结构,每个节点包含数据和指向下一个节点的指针。React 使用链表来组织 Fiber 节点和更新队列。
链表的基本结构:
// 单向链表节点
class ListNode {
constructor(value) {
this.value = value;
this.next = null;
}
}
// 双向链表节点
class DoublyListNode {
constructor(value) {
this.value = value;
this.prev = null;
this.next = null;
}
}
// 循环链表
class CircularListNode {
constructor(value) {
this.value = value;
this.next = this; // 指向自己,形成环
}
}
链表的基本操作:
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
// 在头部插入
prepend(value) {
const newNode = new ListNode(value);
if (this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.next = this.head;
this.head = newNode;
}
this.size++;
}
// 在尾部插入
append(value) {
const newNode = new ListNode(value);
if (this.tail === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.size++;
}
// 删除节点
remove(value) {
if (this.head === null) {
return null;
}
// 如果要删除的是头节点
if (this.head.value === value) {
const removed = this.head;
this.head = this.head.next;
if (this.head === null) {
this.tail = null;
}
this.size--;
return removed;
}
// 查找要删除的节点
let current = this.head;
while (current.next !== null) {
if (current.next.value === value) {
const removed = current.next;
current.next = current.next.next;
if (removed === this.tail) {
this.tail = current;
}
this.size--;
return removed;
}
current = current.next;
}
return null;
}
// 遍历链表
traverse(callback) {
let current = this.head;
while (current !== null) {
callback(current.value);
current = current.next;
}
}
}
// 使用示例
const list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);
list.prepend(0);
list.traverse(value => console.log(value)); // 0, 1, 2, 3
React 中的应用
fiber
Fiber 架构使用链表结构来组织 Fiber 树,每个 Fiber 节点通过三个指针连接:child(第一个子节点)、sibling(兄弟节点)、return(父节点)。
// Fiber 节点的链表结构
const fiber = {
// 链表指针
return: null, // 指向父 Fiber(链表的前驱)
child: null, // 指向第一个子 Fiber(子链表的头)
sibling: null, // 指向下一个兄弟 Fiber(链表的后继)
// 节点数据
tag: WorkTag,
type: any,
stateNode: any,
// 其他属性...
};
Fiber 树的链表结构示例:
// JSX 结构
<div>
<h1>Title</h1>
<p>Content</p>
<span>Footer</span>
</div>
// Fiber 链表结构
const divFiber = {
type: 'div',
child: h1Fiber, // 指向第一个子节点
sibling: null, // 没有兄弟节点
return: parentFiber // 指向父节点
};
const h1Fiber = {
type: 'h1',
child: textFiber1,
sibling: pFiber, // 指向兄弟节点 p
return: divFiber // 指向父节点 div
};
const pFiber = {
type: 'p',
child: textFiber2,
sibling: spanFiber, // 指向兄弟节点 span
return: divFiber
};
const spanFiber = {
type: 'span',
child: textFiber3,
sibling: null, // 最后一个兄弟
return: divFiber
};
Fiber 树的遍历(深度优先):
// 遍历 Fiber 树
function traverseFiberTree(fiber) {
let current = fiber;
while (current !== null) {
// 处理当前节点
console.log(current.type);
// 1. 优先遍历子节点
if (current.child !== null) {
current = current.child;
continue;
}
// 2. 没有子节点,遍历兄弟节点
if (current.sibling !== null) {
current = current.sibling;
continue;
}
// 3. 回到父节点,找父节点的兄弟节点
while (current.return !== null) {
current = current.return;
if (current.sibling !== null) {
current = current.sibling;
break;
}
}
// 如果回到根节点且没有兄弟,遍历结束
if (current.return === null && current.sibling === null) {
break;
}
}
}
Fiber 链表遍历流程:
graph TD
A["div"] --> B["h1"]
B --> C["text: Title"]
B --> D["p"]
D --> E["text: Content"]
D --> F["span"]
F --> G["text: Footer"]
A -.child.-> B
B -.child.-> C
B -.sibling.-> D
D -.child.-> E
D -.sibling.-> F
F -.child.-> G
style A fill:#61dafb
style B fill:#ffd700
style D fill:#ffd700
style F fill:#ffd700
UpdateQueue
UpdateQueue 使用循环链表来存储更新对象,确保更新按顺序执行。
// Update 对象(链表节点)
const update = {
eventTime: number, // 更新触发时间
lane: Lane, // 更新优先级
tag: UpdateTag, // 更新类型
payload: any, // 更新的数据
callback: Function | null, // 更新完成后的回调
next: null, // 指向下一个 Update(循环链表)
};
// UpdateQueue 结构
const updateQueue = {
baseState: any, // 基础状态
firstBaseUpdate: null, // 第一个未处理的 Update
lastBaseUpdate: null, // 最后一个未处理的 Update
shared: {
pending: null, // 新的 Update 循环链表
},
effects: null, // 有 callback 的 Update 列表
};
// 创建 UpdateQueue
function initializeUpdateQueue(fiber) {
const queue = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}
// 将 Update 加入队列(循环链表)
function enqueueUpdate(fiber, update) {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
return;
}
const sharedQueue = updateQueue.shared;
const pending = sharedQueue.pending;
if (pending === null) {
// 第一个 Update,指向自己形成环
update.next = update;
} else {
// 插入到环中
update.next = pending.next;
pending.next = update;
}
// pending 始终指向最后一个 Update
// pending.next 指向第一个 Update
sharedQueue.pending = update;
}
// 处理 UpdateQueue
function processUpdateQueue(workInProgress, props) {
const queue = workInProgress.updateQueue;
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
// 获取待处理的 Update 链表
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
queue.shared.pending = null;
// 断开循环链表
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
// 将新的 Update 链接到 baseUpdate 后面
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
}
// 处理所有 Update
if (firstBaseUpdate !== null) {
let newState = queue.baseState;
let update = firstBaseUpdate;
do {
const updateLane = update.lane;
// 根据优先级判断是否处理此 Update
if (isSubsetOfLanes(renderLanes, updateLane)) {
// 处理 Update
newState = getStateFromUpdate(update, newState, props);
// 如果有回调,加入 effects
const callback = update.callback;
if (callback !== null) {
workInProgress.flags |= Callback;
update.nextEffect = null;
if (queue.effects === null) {
queue.effects = [update];
} else {
queue.effects.push(update);
}
}
}
update = update.next;
if (update === null) {
break;
}
} while (true);
queue.baseState = newState;
queue.firstBaseUpdate = firstBaseUpdate;
queue.lastBaseUpdate = lastBaseUpdate;
workInProgress.memoizedState = newState;
}
}
UpdateQueue 循环链表示意图:
graph LR
A["pending"] --> B["Update 3"]
B --> C["Update 1"]
C --> D["Update 2"]
D --> B
E["firstBaseUpdate"] --> F["Update 4"]
F --> G["Update 5"]
G --> H["null"]
style A fill:#ff6b6b
style E fill:#51cf66
栈
基本使用
栈是一种后进先出(LIFO)的数据结构。在 React 中,栈主要用于管理执行上下文和状态。
// 栈的基本实现
class Stack {
constructor() {
this.items = [];
}
// 入栈
push(element) {
this.items.push(element);
}
// 出栈
pop() {
if (this.isEmpty()) {
return null;
}
return this.items.pop();
}
// 查看栈顶元素
peek() {
if (this.isEmpty()) {
return null;
}
return this.items[this.items.length - 1];
}
// 判断栈是否为空
isEmpty() {
return this.items.length === 0;
}
// 获取栈大小
size() {
return this.items.length;
}
// 清空栈
clear() {
this.items = [];
}
}
// 使用示例
const stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.peek()); // 3
console.log(stack.pop()); // 3
console.log(stack.pop()); // 2
console.log(stack.size()); // 1
React 中的应用
Context 状态管理
React 使用栈来管理 Context 的值,支持嵌套的 Context Provider。
// Context 值栈
const valueStack = [];
let index = -1;
// 创建 Context
function createContext(defaultValue) {
const context = {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: defaultValue,
_currentValue2: defaultValue,
Provider: null,
Consumer: null,
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
context.Consumer = context;
return context;
}
// 入栈:保存当前值,设置新值
function pushProvider(providerFiber, nextValue) {
const context = providerFiber.type._context;
// 将当前值压入栈
push(valueStack, context._currentValue);
// 设置新值
context._currentValue = nextValue;
}
// 出栈:恢复之前的值
function popProvider(providerFiber) {
const context = providerFiber.type._context;
// 从栈中弹出之前的值
const currentValue = pop(valueStack);
// 恢复之前的值
context._currentValue = currentValue;
}
// 栈操作的辅助函数
function push(cursor, value) {
index++;
valueStack[index] = value;
}
function pop(cursor) {
if (index < 0) {
return;
}
const value = valueStack[index];
valueStack[index] = null;
index--;
return value;
}
// 读取 Context 值
function readContext(context) {
// 返回栈顶的值(当前生效的值)
return context._currentValue;
}
Context 嵌套示例:
const ThemeContext = createContext('light');
const UserContext = createContext(null);
function App() {
return (
<ThemeContext.Provider value="dark">
<UserContext.Provider value={{ name: 'Alice' }}>
<ThemeContext.Provider value="blue">
<Child />
</ThemeContext.Provider>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// Render 阶段,Context 值栈的变化:
// 1. pushProvider(ThemeContext, 'dark')
// 栈: ['light']
// 当前值: 'dark'
//
// 2. pushProvider(UserContext, { name: 'Alice' })
// 栈: ['light', null]
// 当前值: { name: 'Alice' }
//
// 3. pushProvider(ThemeContext, 'blue')
// 栈: ['light', null, 'dark']
// 当前值: 'blue'
//
// 4. 渲染 <Child />,读取 Context
// ThemeContext._currentValue = 'blue'
// UserContext._currentValue = { name: 'Alice' }
//
// 5. popProvider(ThemeContext)
// 栈: ['light', null]
// 恢复值: 'dark'
//
// 6. popProvider(UserContext)
// 栈: ['light']
// 恢复值: null
//
// 7. popProvider(ThemeContext)
// 栈: []
// 恢复值: 'light'
Context 栈的可视化:
graph TB
subgraph "Context 栈的变化"
A["初始状态<br/>栈: 空<br/>ThemeContext: light"] --> B["Provider value=dark<br/>栈: light<br/>ThemeContext: dark"]
B --> C["Provider value=blue<br/>栈: light, dark<br/>ThemeContext: blue"]
C --> D["渲染子组件<br/>读取 blue"]
D --> E["popProvider<br/>栈: light, dark<br/>恢复为: dark"]
E --> F["popProvider<br/>栈: light<br/>恢复为: light"]
end
style A fill:#61dafb
style D fill:#ffd700
style F fill:#51cf66
算法
diff 过程(调和)
Diff 算法是 React 协调(Reconciliation)过程的核心,用于比较新旧 Fiber 树,找出需要更新的节点,从而最小化 DOM 操作。
React Diff 算法的三个策略:
- 树层级比较:只比较同层级节点,不跨层级比较
- 组件类型比较:不同类型的组件会生成不同的树结构
- 列表比较:通过 key 标识列表中的元素
graph TB
A["Diff 算法"] --> B["单节点 Diff"]
A --> C["多节点 Diff"]
B --> B1["key 和 type 都相同<br/>复用节点"]
B --> B2["key 相同 type 不同<br/>删除旧节点 创建新节点"]
B --> B3["key 不同<br/>删除旧节点 创建新节点"]
C --> C1["第一轮遍历<br/>处理更新节点"]
C --> C2["第二轮遍历<br/>处理剩余新节点"]
C --> C3["第三轮遍历<br/>删除剩余旧节点"]
style B fill:#61dafb
style C fill:#ffd700
单节点 Diff 算法:
// 单节点 Diff:新节点只有一个子节点
function reconcileSingleElement(
returnFiber,
currentFirstChild,
element
) {
const key = element.key;
let child = currentFirstChild;
// 遍历旧的子节点
while (child !== null) {
// 1. 比较 key
if (child.key === key) {
// 2. key 相同,比较 type
if (child.elementType === element.type) {
// key 和 type 都相同,可以复用
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.ref = element.ref;
existing.return = returnFiber;
return existing;
} else {
// key 相同但 type 不同,删除所有旧节点
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
// key 不同,删除这个旧节点,继续比较下一个
deleteChild(returnFiber, child);
}
child = child.sibling;
}
// 没有可复用的节点,创建新节点
const created = createFiberFromElement(element);
created.ref = element.ref;
created.return = returnFiber;
return created;
}
// 示例:单节点 Diff
// 旧:<div key="a">A</div>
// 新:<div key="a">B</div>
// 结果:复用 div 节点,更新内容
// 旧:<div key="a">A</div>
// 新:<span key="a">A</span>
// 结果:删除 div,创建新的 span
// 旧:<div key="a">A</div>
// 新:<div key="b">B</div>
// 结果:删除旧 div,创建新 div
多节点 Diff 算法:
// 多节点 Diff:新节点有多个子节点
function reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChildren
) {
let resultingFirstChild = null;
let previousNewFiber = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// 第一轮遍历:处理更新的节点
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx]
);
if (newFiber === null) {
// key 不同,跳出第一轮遍历
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// 新节点没有复用旧节点,删除旧节点
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// 新节点已经遍历完
if (newIdx === newChildren.length) {
// 删除剩余的旧节点
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
// 旧节点已经遍历完
if (oldFiber === null) {
// 创建剩余的新节点
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx]);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// 第二轮遍历:处理移动的节点
// 将剩余的旧节点放入 Map
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx]
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// 复用了旧节点,从 Map 中删除
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// 删除 Map 中剩余的旧节点
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
Diff 算法实例分析:
// 场景 1:节点更新
// 旧:[A, B, C]
// 新:[A, B, D]
// 操作:复用 A、B,删除 C,创建 D
// 场景 2:节点移动
// 旧:[A, B, C]
// 新:[C, A, B]
// 操作:移动 C 到前面,A、B 保持不变
// 场景 3:节点插入
// 旧:[A, C]
// 新:[A, B, C]
// 操作:复用 A、C,在中间插入 B
// 场景 4:节点删除
// 旧:[A, B, C]
// 新:[A, C]
// 操作:复用 A、C,删除 B
// 详细示例:节点移动
const oldChildren = [
{ key: 'a', index: 0 },
{ key: 'b', index: 1 },
{ key: 'c', index: 2 },
{ key: 'd', index: 3 }
];
const newChildren = [
{ key: 'd' },
{ key: 'a' },
{ key: 'b' },
{ key: 'c' }
];
// Diff 过程:
// 1. 第一轮遍历
// - 比较 d 和 a,key 不同,跳出
//
// 2. 将剩余旧节点放入 Map
// Map: { a: fiber_a, b: fiber_b, c: fiber_c, d: fiber_d }
//
// 3. 第二轮遍历
// - 处理 d:在 Map 中找到,oldIndex=3, lastPlacedIndex=0
// oldIndex(3) > lastPlacedIndex(0),不需要移动
// 更新 lastPlacedIndex = 3
//
// - 处理 a:在 Map 中找到,oldIndex=0, lastPlacedIndex=3
// oldIndex(0) < lastPlacedIndex(3),需要移动
// 标记 Placement
//
// - 处理 b:在 Map 中找到,oldIndex=1, lastPlacedIndex=3
// oldIndex(1) < lastPlacedIndex(3),需要移动
// 标记 Placement
//
// - 处理 c:在 Map 中找到,oldIndex=2, lastPlacedIndex=3
// oldIndex(2) < lastPlacedIndex(3),需要移动
// 标记 Placement
//
// 最终:d 不动,a、b、c 移动到 d 后面
Diff 算法流程图:
flowchart TD
A["开始 Diff"] --> B{"节点数量"}
B -->|单节点| C["单节点 Diff"]
B -->|多节点| D["多节点 Diff"]
C --> C1{"key 是否相同"}
C1 -->|是| C2{"type 是否相同"}
C2 -->|是| C3["复用节点"]
C2 -->|否| C4["删除旧节点 创建新节点"]
C1 -->|否| C4
D --> D1["第一轮遍历<br/>更新节点"]
D1 --> D2{"新节点遍历完?"}
D2 -->|是| D3["删除剩余旧节点"]
D2 -->|否| D4{"旧节点遍历完?"}
D4 -->|是| D5["创建剩余新节点"]
D4 -->|否| D6["第二轮遍历<br/>处理移动"]
D6 --> D7["删除未复用的旧节点"]
D3 --> E["结束"]
D5 --> E
D7 --> E
C3 --> E
C4 --> E
style C3 fill:#51cf66
style E fill:#61dafb
位运算
位运算基础
位运算是直接对二进制位进行操作的运算,执行速度极快。React 使用位运算来管理 Fiber 的标记(flags)和优先级(lanes)。
JavaScript 的位运算符:
// 1. 按位与(AND):&
// 两位都为 1 时结果为 1
5 & 3 // 0101 & 0011 = 0001 = 1
// 2. 按位或(OR):|
// 两位有一个为 1 时结果为 1
5 | 3 // 0101 | 0011 = 0111 = 7
// 3. 按位异或(XOR):^
// 两位不同时结果为 1
5 ^ 3 // 0101 ^ 0011 = 0110 = 6
// 4. 按位非(NOT):~
// 对每一位取反
~5 // ~0101 = 1010 = -6(补码)
// 5. 左移:<<
// 向左移动指定位数,右边补 0
5 << 1 // 0101 << 1 = 1010 = 10
// 6. 有符号右移:>>
// 向右移动指定位数,左边补符号位
5 >> 1 // 0101 >> 1 = 0010 = 2
// 7. 无符号右移:>>>
// 向右移动指定位数,左边补 0
-5 >>> 1 // 正数
位运算的常见技巧:
// 1. 判断奇偶
const isOdd = (n) => !!(n & 1);
isOdd(5); // true
isOdd(6); // false
// 2. 交换两个数(不使用临时变量)
let a = 5, b = 3;
a ^= b; // a = 5 ^ 3
b ^= a; // b = 3 ^ (5 ^ 3) = 5
a ^= b; // a = (5 ^ 3) ^ 5 = 3
// 结果:a = 3, b = 5
// 3. 判断 2 的幂
const isPowerOfTwo = (n) => n > 0 && (n & (n - 1)) === 0;
isPowerOfTwo(8); // true (1000 & 0111 = 0)
isPowerOfTwo(10); // false
// 4. 获取最低位的 1
const getLowestBit = (n) => n & -n;
getLowestBit(12); // 4 (1100 & 0100 = 0100)
// 5. 清除最低位的 1
const clearLowestBit = (n) => n & (n - 1);
clearLowestBit(12); // 8 (1100 & 1011 = 1000)
// 6. 统计 1 的个数
function countOnes(n) {
let count = 0;
while (n) {
n &= (n - 1); // 每次清除最低位的 1
count++;
}
return count;
}
countOnes(7); // 3 (0111 有 3 个 1)
位运算求解
位运算在算法题中的应用非常广泛,可以高效地解决特定问题。
经典位运算问题:
// 1. 找出数组中唯一出现一次的数字(其他数字都出现两次)
function singleNumber(nums) {
let result = 0;
for (let num of nums) {
result ^= num; // 异或运算,相同的数会抵消
}
return result;
}
singleNumber([4, 1, 2, 1, 2]); // 4
// 解释:4 ^ 1 ^ 2 ^ 1 ^ 2 = 4 ^ (1 ^ 1) ^ (2 ^ 2) = 4 ^ 0 ^ 0 = 4
// 2. 不使用加减乘除实现加法
function add(a, b) {
while (b !== 0) {
const carry = (a & b) << 1; // 计算进位
a = a ^ b; // 计算不带进位的和
b = carry; // 将进位赋值给 b
}
return a;
}
add(5, 3); // 8
// 解释:
// 5 = 0101, 3 = 0011
// 第一次:carry = 0010 << 1 = 0100, a = 0110, b = 0100
// 第二次:carry = 0100 << 1 = 1000, a = 0010, b = 1000
// 第三次:carry = 0000 << 1 = 0000, a = 1000, b = 0000
// 结果:1000 = 8
// 3. 数字范围按位与
function rangeBitwiseAnd(left, right) {
let shift = 0;
// 找到公共前缀
while (left < right) {
left >>= 1;
right >>= 1;
shift++;
}
return left << shift;
}
rangeBitwiseAnd(5, 7); // 4
// 5 = 101, 6 = 110, 7 = 111
// 公共前缀:1,结果:100 = 4
// 4. 位 1 的个数(汉明重量)
function hammingWeight(n) {
let count = 0;
while (n !== 0) {
count++;
n &= (n - 1); // 清除最低位的 1
}
return count;
}
hammingWeight(11); // 3 (1011 有 3 个 1)
React 中的应用
React 在 Fiber 架构中大量使用位运算来管理标记(flags)和优先级(lanes),提高性能。
1. Fiber Flags(副作用标记)
// Fiber 的副作用标记(使用位运算)
const NoFlags = 0b0000000000000000000000000;
const PerformedWork = 0b0000000000000000000000001;
const Placement = 0b0000000000000000000000010;
const Update = 0b0000000000000000000000100;
const Deletion = 0b0000000000000000000001000;
const ChildDeletion = 0b0000000000000000000010000;
const ContentReset = 0b0000000000000000000100000;
const Callback = 0b0000000000000000001000000;
const Ref = 0b0000000000000000100000000;
const Snapshot = 0b0000000000000001000000000;
const Passive = 0b0000000000000010000000000;
// 添加标记
fiber.flags |= Placement;
fiber.flags |= Update;
// 检查是否有某个标记
const hasPlacement = (fiber.flags & Placement) !== 0;
const hasUpdate = (fiber.flags & Update) !== 0;
// 移除标记
fiber.flags &= ~Placement;
// 检查是否有多个标记之一
const hasPlacementOrUpdate = (fiber.flags & (Placement | Update)) !== 0;
// 示例:管理 Fiber 的副作用
function commitMutationEffects(fiber) {
const flags = fiber.flags;
// 检查是否有内容重置标记
if (flags & ContentReset) {
commitResetTextContent(fiber);
}
// 检查是否有 ref 更新
if (flags & Ref) {
const current = fiber.alternate;
if (current !== null) {
commitDetachRef(current);
}
}
// 检查主要副作用
const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
switch (primaryFlags) {
case Placement: {
commitPlacement(fiber);
// 清除 Placement 标记
fiber.flags &= ~Placement;
break;
}
case Update: {
const current = fiber.alternate;
commitWork(current, fiber);
break;
}
// ... 其他情况
}
}
2. Lanes(优先级模型)
// Lane 优先级(使用位运算)
const NoLanes = 0b0000000000000000000000000000000;
const SyncLane = 0b0000000000000000000000000000001;
const InputContinuousLane = 0b0000000000000000000000000000100;
const DefaultLane = 0b0000000000000000000000000010000;
const TransitionLanes = 0b0000000011111111111111111000000;
const IdleLane = 0b0100000000000000000000000000000;
// 检查是否包含某个 Lane
function includesSomeLane(a, b) {
return (a & b) !== 0;
}
// 检查是否是 Lane 的子集
function isSubsetOfLanes(set, subset) {
return (set & subset) === subset;
}
// 合并 Lanes
function mergeLanes(a, b) {
return a | b;
}
// 移除 Lanes
function removeLanes(set, subset) {
return set & ~subset;
}
// 获取最高优先级的 Lane
function getHighestPriorityLane(lanes) {
return lanes & -lanes; // 获取最低位的 1
}
// 示例:调度更新
function scheduleUpdateOnFiber(fiber, lane) {
// 将 lane 合并到 fiber 的 lanes
fiber.lanes = mergeLanes(fiber.lanes, lane);
// 向上标记父节点的 childLanes
let parent = fiber.return;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
parent = parent.return;
}
// 调度根节点更新
ensureRootIsScheduled(root);
}
// 处理更新时检查优先级
function beginWork(current, workInProgress, renderLanes) {
const updateLanes = workInProgress.lanes;
// 检查当前渲染是否包含这个 fiber 的更新
if (!includesSomeLane(renderLanes, updateLanes)) {
// 优先级不够,跳过这个 fiber
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// 处理 fiber...
}
位运算优化对比:
// 传统方式:使用对象或数组管理标记
const flags = {
placement: false,
update: false,
deletion: false
};
// 检查标记
if (flags.placement) { /* ... */ }
// 添加标记
flags.update = true;
// ❌ 问题:
// 1. 占用更多内存
// 2. 访问速度较慢
// 3. 难以批量操作
// React 方式:使用位运算
let flags = 0;
// 添加标记(一次操作)
flags |= Placement | Update; // 0b110
// 检查多个标记(一次操作)
if (flags & (Placement | Update | Deletion)) { /* ... */ }
// 移除标记(一次操作)
flags &= ~Placement;
// ✅ 优势:
// 1. 内存占用小(只需一个数字)
// 2. 操作速度快(位运算是底层指令)
// 3. 可以轻松批量操作多个标记
图遍历
概念
图遍历是访问图中所有节点的过程。常见的图遍历算法有深度优先搜索(DFS)和广度优先搜索(BFS)。
图的基本概念:
// 图的表示方式
// 1. 邻接矩阵
const graph = [
[0, 1, 1, 0], // 节点 0 连接到节点 1、2
[1, 0, 0, 1], // 节点 1 连接到节点 0、3
[1, 0, 0, 1], // 节点 2 连接到节点 0、3
[0, 1, 1, 0] // 节点 3 连接到节点 1、2
];
// 2. 邻接表
const graph = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2]
};
// 3. 边列表
const edges = [
[0, 1],
[0, 2],
[1, 3],
[2, 3]
];
实现方式
深度优先搜索(DFS):
// DFS 递归实现
function dfsRecursive(graph, start, visited = new Set()) {
console.log(start); // 访问节点
visited.add(start);
const neighbors = graph[start] || [];
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
dfsRecursive(graph, neighbor, visited);
}
}
}
// DFS 迭代实现(使用栈)
function dfsIterative(graph, start) {
const visited = new Set();
const stack = [start];
while (stack.length > 0) {
const node = stack.pop();
if (!visited.has(node)) {
console.log(node); // 访问节点
visited.add(node);
const neighbors = graph[node] || [];
// 逆序入栈,保证遍历顺序
for (let i = neighbors.length - 1; i >= 0; i--) {
stack.push(neighbors[i]);
}
}
}
}
// 示例
const graph = {
0: [1, 2],
1: [0, 3, 4],
2: [0, 5],
3: [1],
4: [1],
5: [2]
};
dfsRecursive(graph, 0); // 输出:0, 1, 3, 4, 2, 5
广度优先搜索(BFS):
// BFS 实现(使用队列)
function bfs(graph, start) {
const visited = new Set();
const queue = [start];
visited.add(start);
while (queue.length > 0) {
const node = queue.shift();
console.log(node); // 访问节点
const neighbors = graph[node] || [];
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
}
// 带层级的 BFS
function bfsWithLevel(graph, start) {
const visited = new Set();
const queue = [[start, 0]]; // [节点, 层级]
visited.add(start);
while (queue.length > 0) {
const [node, level] = queue.shift();
console.log(`节点 ${node},层级 ${level}`);
const neighbors = graph[node] || [];
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push([neighbor, level + 1]);
}
}
}
}
// 示例
bfs(graph, 0); // 输出:0, 1, 2, 3, 4, 5
DFS vs BFS 对比:
graph TB
A[遍历算法] --> B[DFS 深度优先]
A --> C[BFS 广度优先]
B --> B1[特点]
B1 --> B11[先深入到底]
B1 --> B12[使用栈/递归]
B1 --> B13["空间复杂度: O(height)"]
B --> B2[应用场景]
B2 --> B21[拓扑排序]
B2 --> B22[检测环]
B2 --> B23[路径查找]
C --> C1[特点]
C1 --> C11[逐层遍历]
C1 --> C12[使用队列]
C1 --> C13["空间复杂度: O(width)"]
C --> C2[应用场景]
C2 --> C21[最短路径]
C2 --> C22[层序遍历]
C2 --> C23[连通性检测]
style B fill:#61dafb
style C fill:#ffd700
React 中的应用
fiber 树的构造
Fiber 树的构建过程本质上是对虚拟 DOM 树的深度优先遍历(DFS)。
// Fiber 树的 DFS 遍历
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
// 1. beginWork:处理当前节点,返回子节点
let next = beginWork(current, unitOfWork, renderLanes);
if (next === null) {
// 2. 没有子节点,执行 completeWork
completeUnitOfWork(unitOfWork);
} else {
// 3. 有子节点,继续向下遍历
workInProgress = next;
}
}
function completeUnitOfWork(unitOfWork) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
// 完成当前节点的工作
completeWork(current, completedWork, renderLanes);
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 有兄弟节点,遍历兄弟节点
workInProgress = siblingFiber;
return;
}
// 没有兄弟节点,回到父节点
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
Fiber 树遍历顺序示例:
// JSX 结构
<App>
<Header>
<h1>Title</h1>
</Header>
<Content>
<p>Text</p>
<button>Click</button>
</Content>
</App>
// DFS 遍历顺序(beginWork 阶段):
// 1. App (beginWork)
// 2. ├─ Header (beginWork)
// 3. │ └─ h1 (beginWork)
// 4. │ └─ "Title" (beginWork)
// 5. │ └─ "Title" (completeWork)
// 6. │ └─ h1 (completeWork)
// 7. ├─ Header (completeWork)
// 8. ├─ Content (beginWork)
// 9. │ ├─ p (beginWork)
// 10. │ │ └─ "Text" (beginWork)
// 11. │ │ └─ "Text" (completeWork)
// 12. │ ├─ p (completeWork)
// 13. │ ├─ button (beginWork)
// 14. │ │ └─ "Click" (beginWork)
// 15. │ │ └─ "Click" (completeWork)
// 16. │ ├─ button (completeWork)
// 17. ├─ Content (completeWork)
// 18. App (completeWork)
Fiber 树遍历流程图:
graph TD
A["开始: workInProgress = rootFiber"] --> B["performUnitOfWork"]
B --> C["beginWork 处理当前节点"]
C --> D{"有子节点?"}
D -->|是| E["workInProgress = child"]
E --> B
D -->|否| F["completeWork 完成当前节点"]
F --> G{"有兄弟节点?"}
G -->|是| H["workInProgress = sibling"]
H --> B
G -->|否| I{"有父节点?"}
I -->|是| J["workInProgress = parent"]
J --> F
I -->|否| K["遍历完成"]
style A fill:#61dafb
style K fill:#51cf66
查找 context 的消费节点
当 Context 的值发生变化时,React 需要找到所有消费该 Context 的组件并触发更新。这是一个深度优先搜索过程。
// 查找并标记 Context 的消费者
function propagateContextChange(workInProgress, context, renderLanes) {
let fiber = workInProgress.child;
if (fiber !== null) {
fiber.return = workInProgress;
}
// DFS 遍历子树
while (fiber !== null) {
let nextFiber;
// 检查当前 fiber 是否消费了这个 Context
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
let dependency = list.firstContext;
while (dependency !== null) {
// 找到消费了这个 Context 的 fiber
if (dependency.context === context) {
// 标记需要更新
if (fiber.tag === ClassComponent) {
// 创建 forceUpdate 的 Update
const update = createUpdate(renderLanes);
update.tag = ForceUpdate;
enqueueUpdate(fiber, update);
}
// 将这个 fiber 的 lanes 合并到 renderLanes
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
// 向上标记父节点的 childLanes
scheduleContextWorkOnParentPath(
fiber.return,
renderLanes,
workInProgress
);
// 标记依赖列表
list.lanes = mergeLanes(list.lanes, renderLanes);
break;
}
dependency = dependency.next;
}
} else if (fiber.tag === ContextProvider) {
// 如果遇到同类型的 Provider,不需要继续向下查找
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else {
nextFiber = fiber.child;
}
if (nextFiber !== null) {
// 有子节点,继续向下
nextFiber.return = fiber;
} else {
// 没有子节点,查找兄弟或回到父节点
nextFiber = fiber;
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
// 回到了起点,搜索结束
nextFiber = null;
break;
}
const sibling = nextFiber.sibling;
if (sibling !== null) {
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}
// 使用示例
const ThemeContext = React.createContext('light');
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Header />
<Content>
<Sidebar />
<Main />
</Content>
</ThemeContext.Provider>
);
}
function Header() {
const theme = useContext(ThemeContext); // 消费 Context
return <header className={theme}>Header</header>;
}
function Main() {
const theme = useContext(ThemeContext); // 消费 Context
return <main className={theme}>Main</main>;
}
// 当 theme 变化时:
// 1. propagateContextChange 从 Provider 开始 DFS
// 2. 遍历 Header、Content、Sidebar、Main
// 3. 找到 Header 和 Main 消费了 ThemeContext
// 4. 标记 Header 和 Main 需要更新
// 5. 向上标记所有祖先节点的 childLanes
Context 消费者查找流程:
graph TD
A["Context.Provider 值变化"] --> B["propagateContextChange"]
B --> C["DFS 遍历子树"]
C --> D{"检查当前 Fiber"}
D -->|有 dependencies| E{"是否消费此 Context?"}
E -->|是| F["标记 Fiber 需要更新"]
E -->|否| G["继续遍历"]
D -->|无 dependencies| G
D -->|是 Provider| H{"是否同类型?"}
H -->|是| I["停止向下查找"]
H -->|否| G
F --> J["向上标记 childLanes"]
J --> G
G --> K{"有子节点?"}
K -->|是| C
K -->|否| L{"有兄弟节点?"}
L -->|是| C
L -->|否| M{"回到父节点"}
M --> L
style A fill:#61dafb
style F fill:#ffd700
style J fill:#51cf66