React 性能优化
React应用性能优化策略
前置知识
- 状态管理方案对比:建议先完成前一篇的学习
学习目标
- 掌握「1. 历史动机与发展脉络」的核心机制、典型用法与常见陷阱
- 掌握「2. 形式化定义」的核心机制、典型用法与常见陷阱
- 掌握「3. 理论推导与原理解析」的核心机制、典型用法与常见陷阱
- 掌握「4. 代码示例(企业级 Production-Ready)」的核心机制、典型用法与常见陷阱
- 掌握「5. 对比分析」的核心机制、典型用法与常见陷阱
本章对标 MIT 6.S192(Software Performance Engineering)与 Stanford CS142(Web Applications)课程深度,系统阐述 React 应用性能优化的形式化原理、工程方法与案例研究。读者将在理解 Fiber 架构、协调算法与并发模式的基础上,掌握可观测、可度量、可复现的性能工程体系。
1. 历史动机与发展脉络
1.1 React 性能工程的演进时间线
React 自 2013 年开源以来,其性能模型经历了四次范式跃迁:
-
2013–2016(v0.3 → v15):同步递归渲染
- 采用递归
mountComponent/receiveComponent调用栈,渲染过程不可中断。 - 一旦组件树深度过大(>30 层)或子节点数量庞大(>1000 个),主线程被长时间占用,导致交互卡顿。
- 优化手段主要依赖
shouldComponentUpdate(SCU)与PureComponent,开发者需手动比较 props。
- 采用递归
-
2017(v16.0):Fiber 架构
- 重写核心调度算法,将递归调用栈改造为可中断的链表遍历。
- 引入工作循环(Work Loop)、优先级调度(Priority Scheduling)与时间切片(Time Slicing)的概念。
- 详见
react/Fiber架构.md。
-
2019(v16.8):Hooks
- 函数组件获得状态与副作用能力,记忆化原语(
useMemo、useCallback)成为主流优化手段。 - 但手动维护依赖数组(dependency array)带来认知负担与潜在 Bug。
- 函数组件获得状态与副作用能力,记忆化原语(
-
2022(v18.0):并发特性(Concurrent Features)
- 并发渲染(Concurrent Rendering)、自动批处理(Automatic Batching)、过渡(Transitions)正式 GA。
useTransition、useDeferredValue让开发者能够显式标记非紧急更新。
-
2024–2025(v19 + React Compiler)
- React Compiler(原 React Forget)进入稳定阶段,通过编译期自动插入记忆化代码,消除手动
useMemo/useCallback的需求。 - Server Components、Actions、
useOptimistic等进一步将性能边界前移至服务端。
- React Compiler(原 React Forget)进入稳定阶段,通过编译期自动插入记忆化代码,消除手动
1.2 Meta(Facebook)的设计哲学
React 的性能哲学可归纳为三条原则:
- 声明式优先于命令式:开发者描述 UI 应当是什么,框架负责高效地将其与 DOM 同步。
- 可预测性优先于极限性能:React 选择”每次状态变更都重新渲染整个子树”的简单模型,再通过记忆化与协调算法优化。这避免了 Vue/Angular 细粒度依赖追踪带来的运行时开销与不可预测性。
- 渐进式复杂度:从
React.memo到并发模式再到 Compiler,每一层抽象都向后兼容,开发者可按需启用。
1.3 性能优化的三层次模型
参考 Brendan Gregg 的 USE 方法(Utilization/Saturation/Errors)与 Google 的 FLIGHT 模型,我们将 React 性能优化划分为三个层次:
| 层次 | 关注点 | 典型指标 | 工具 |
|---|---|---|---|
| L1 渲染层 | 组件树渲染效率 | Render duration、Commit duration、Re-render count | React Profiler |
| L2 运行时层 | 主线程占用、长任务 | INP、TBT、Long Task 数量 | Chrome Performance |
| L3 网络与加载层 | 资源体积、首屏时间 | LCP、FCP、TTI、Bundle size | Lighthouse、WebPageTest |
2. 形式化定义
2.1 渲染过程的数学建模
设组件树 ,其中 为节点集合(Fiber 节点), 为父子关系。一次状态更新触发从根节点 开始的渲染过程,可形式化为:
其中 为节点 的渲染开销(执行函数体、计算 JSX), 为提交开销(DOM 操作、ref 回调、生命周期)。
React 的协调算法(Reconciliation)通过 同层比较 + key 标识 将朴素的 树编辑距离问题降为 :
2.2 记忆化的代数语义
React.memo 等价于在组件函数 外层包装一个记忆化包装器 :
其中 表示浅比较(shallow equal),即对每个属性 满足 (引用相等)。
useMemo 的语义为:
2.3 虚拟化的复杂度降低
长列表渲染的朴素复杂度为 ,其中 为列表长度。虚拟化通过只渲染可视区域内的 个元素,将 DOM 操作复杂度降为:
内存占用从 ( 为单个节点的内存开销)降至 (仅保留数据引用)。
3. 理论推导与原理解析
3.1 Fiber 调度与时间切片
Fiber 架构的核心是将渲染工作拆分为多个 工作单元(Unit of Work),每个 Fiber 节点对应一个工作单元。React 的工作循环(Work Loop)在每个单元执行后检查是否应该让出主线程:
设一帧预算 (60fps),React 保留约 用于渲染工作,剩余时间分配给浏览器渲染、输入处理等任务:
当 时,React 将工作切片到下一帧执行,避免阻塞交互。
3.2 协调算法的优先级模型
React 18 引入 lanes 优先级模型,用 31 位二进制表示 31 种优先级:
一次更新 被分配到一个 lane :
useTransition 将状态更新标记为低优先级,允许高优先级更新(如用户输入)插队:
const [isPending, startTransition] = useTransition();
function handleSearch(query) {
// 高优先级:立即更新输入框
setInputValue(query);
// 低优先级:搜索结果可延迟
startTransition(() => {
setSearchResults(filterData(query));
});
}
3.3 自动批处理(Automatic Batching)
React 18 之前,批处理仅在 React 事件处理器内生效。React 18 通过 ReactDOM.createRoot 启用自动批处理,所有来源的更新(Promise、setTimeout、原生事件)都会被批处理:
设每次 Render 开销为 ,Commit 开销为 ,批处理前后总开销:
加速比 (理想情况)。
4. 代码示例(企业级 Production-Ready)
4.1 React.memo 配合自定义比较函数
// React 18 + TypeScript 5.x
import React from 'react';
interface UserCardProps {
user: {
id: string;
name: string;
avatar: string;
role: 'admin' | 'user' | 'guest';
};
onSelect: (id: string) => void;
selected: boolean;
}
/**
* UserCard 组件 - 展示用户卡片
* 使用 React.memo + 自定义比较避免不必要的重渲染
*/
const areEqual = (prev: UserCardProps, next: UserCardProps): boolean => {
// 引用相同时直接跳过
if (prev.user === next.user && prev.selected === next.selected) {
return true;
}
// 深比较关键字段
return (
prev.user.id === next.user.id &&
prev.user.name === next.user.name &&
prev.user.avatar === next.user.avatar &&
prev.user.role === next.user.role &&
prev.selected === next.selected &&
prev.onSelect === next.onSelect
);
};
export const UserCard = React.memo(function UserCard({
user,
onSelect,
selected,
}: UserCardProps) {
return (
<div
className={`user-card ${selected ? 'selected' : ''}`}
onClick={() => onSelect(user.id)}
>
<img src={user.avatar} alt={user.name} loading="lazy" />
<span>{user.name}</span>
<span className="role-badge">{user.role}</span>
</div>
);
}, areEqual);
4.2 useDeferredValue 优化搜索
import { useDeferredValue, useMemo, useState } from 'react';
interface SearchResult {
id: string;
title: string;
url: string;
}
/**
* SearchResults - 大数据量搜索组件
* 使用 useDeferredValue 让输入框保持响应
*/
function SearchResults({ results }: { results: SearchResult[] }) {
console.log('SearchResults render, count:', results.length);
return (
<ul>
{results.map((r) => (
<li key={r.id}>
<a href={r.url}>{r.title}</a>
</li>
))}
</ul>
);
}
export default function SearchApp() {
const [query, setQuery] = useState('');
// deferredQuery 在紧急更新后延迟更新
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
const results = useMemo(() => {
return heavyFilter(deferredQuery);
}, [deferredQuery]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索..."
/>
<div style={{ opacity: isStale ? 0.7 : 1 }}>
<SearchResults results={results} />
</div>
</div>
);
}
// 模拟重计算
function heavyFilter(query: string): SearchResult[] {
const all = Array.from({ length: 10000 }, (_, i) => ({
id: String(i),
title: `Item ${i}`,
url: `/items/${i}`,
}));
return all.filter((r) => r.title.includes(query));
}
4.3 虚拟化长列表(react-window)
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { memo } from 'react';
interface Item {
id: string;
name: string;
email: string;
}
const Row = memo(({ data, index, style }: ListChildComponentProps<Item[]>) => {
const item = data[index];
return (
<div style={style} className="list-row">
<span>{item.name}</span>
<span>{item.email}</span>
</div>
);
});
interface VirtualListProps {
items: Item[];
height?: number;
itemSize?: number;
}
export function VirtualList({
items,
height = 600,
itemSize = 50,
}: VirtualListProps) {
return (
<FixedSizeList
height={height}
width="100%"
itemCount={items.length}
itemSize={itemSize}
itemData={items}
>
{Row}
</FixedSizeList>
);
}
// 使用示例
function App() {
const items: Item[] = Array.from({ length: 100000 }, (_, i) => ({
id: String(i),
name: `User ${i}`,
email: `user${i}@example.com`,
}));
return <VirtualList items={items} />;
}
4.4 代码分割与 Suspense
import React, { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router-dom';
// 路由级代码分割
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Profile = lazy(() => import('./pages/Profile'));
const LoadingSpinner = () => (
<div className="loading-spinner" role="status" aria-live="polite">
加载中...
</div>
);
export default function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
);
}
4.5 useTransition 优先级控制
import { useState, useTransition, useMemo } from 'react';
interface Tab {
id: string;
label: string;
data: string[];
}
const TABS: Tab[] = [
{ id: 'all', label: '全部', data: generateData(10000) },
{ id: 'active', label: '活跃', data: generateData(5000) },
{ id: 'archived', label: '归档', data: generateData(20000) },
];
function generateData(n: number): string[] {
return Array.from({ length: n }, (_, i) => `Item ${i}`);
}
export default function TabsView() {
const [activeTab, setActiveTab] = useState('all');
const [isPending, startTransition] = useTransition();
const currentTab = useMemo(
() => TABS.find((t) => t.id === activeTab) ?? TABS[0],
[activeTab]
);
const handleTabClick = (id: string) => {
startTransition(() => {
setActiveTab(id);
});
};
return (
<div>
<div className="tabs">
{TABS.map((tab) => (
<button
key={tab.id}
onClick={() => handleTabClick(tab.id)}
disabled={isPending && activeTab === tab.id}
className={activeTab === tab.id ? 'active' : ''}
>
{tab.label}
</button>
))}
{isPending && <span className="spinner" />}
</div>
<ul style={{ opacity: isPending ? 0.6 : 1 }}>
{currentTab.data.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
);
}
4.6 Profiler API 度量组件渲染
import { Profiler, ProfilerOnRenderCallback, ReactNode } from 'react';
interface PerformanceMonitorProps {
id: string;
children: ReactNode;
onSlowRender?: (duration: number) => void;
threshold?: number;
}
/**
* PerformanceMonitor - 包裹组件,记录渲染耗时
* 当渲染时间超过 threshold 时触发回调
*/
export function PerformanceMonitor({
id,
children,
onSlowRender,
threshold = 16,
}: PerformanceMonitorProps) {
const handleRender: ProfilerOnRenderCallback = (
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) => {
// 实际渲染耗时
console.log(`[${id}] ${phase}: actual=${actualDuration}ms, base=${baseDuration}ms`);
// 上报到监控平台
if (actualDuration > threshold && onSlowRender) {
onSlowRender(actualDuration);
}
// 生产环境上报
if (process.env.NODE_ENV === 'production') {
navigator.sendBeacon('/api/metrics', JSON.stringify({
type: 'react-render',
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
}));
}
};
return <Profiler id={id} onRender={handleRender}>{children}</Profiler>;
}
// 使用
function App() {
return (
<PerformanceMonitor id="dashboard" threshold={50}>
<Dashboard />
</PerformanceMonitor>
);
}
4.7 状态拆分降低重渲染范围
import { useState, useCallback, memo } from 'react';
// 反模式:所有状态在父组件,导致任意变更都触发全部子组件重渲染
function BadExample() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ExpensiveTree data={text} />
</div>
);
}
// 正确模式:将无关状态下沉到子组件
function GoodExample() {
return (
<div>
<Counter />
<TextInput />
</div>
);
}
const Counter = memo(function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
});
const TextInput = memo(function TextInput() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ExpensiveTree data={text} />
</>
);
});
const ExpensiveTree = memo(function ExpensiveTree({ data }: { data: string }) {
// 假设这里有重计算
return <div>{data}</div>;
});
4.8 useReducer 替代多个 useState
import { useReducer, useCallback } from 'react';
interface FormState {
username: string;
email: string;
password: string;
errors: Record<string, string>;
isSubmitting: boolean;
}
type FormAction =
| { type: 'SET_FIELD'; field: keyof FormState; value: string }
| { type: 'SET_ERROR'; field: string; error: string }
| { type: 'START_SUBMIT' }
| { type: 'END_SUBMIT' }
| { type: 'RESET' };
const initialState: FormState = {
username: '',
email: '',
password: '',
errors: {},
isSubmitting: false,
};
function formReducer(state: FormState, action: FormAction): FormState {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value, errors: { ...state.errors, [action.field]: '' } };
case 'SET_ERROR':
return { ...state, errors: { ...state.errors, [action.field]: action.error } };
case 'START_SUBMIT':
return { ...state, isSubmitting: true };
case 'END_SUBMIT':
return { ...state, isSubmitting: false };
case 'RESET':
return initialState;
default:
return state;
}
}
export function useForm() {
const [state, dispatch] = useReducer(formReducer, initialState);
const setField = useCallback((field: keyof FormState, value: string) => {
dispatch({ type: 'SET_FIELD', field, value });
}, []);
const setError = useCallback((field: string, error: string) => {
dispatch({ type: 'SET_ERROR', field, error });
}, []);
const submit = useCallback(async (onSubmit: () => Promise<void>) => {
dispatch({ type: 'START_SUBMIT' });
try {
await onSubmit();
} finally {
dispatch({ type: 'END_SUBMIT' });
}
}, []);
return { state, setField, setError, submit };
}
4.9 React Compiler 自动记忆化
// React 19 + React Compiler
// 无需手动 useMemo/useCallback,Compiler 自动插入记忆化
function ProductList({ products, onSelect, query }) {
// Compiler 自动记忆化 filtered,依赖 products 和 query
const filtered = products.filter((p) => p.name.includes(query));
// Compiler 自动记忆化 handler
const handleClick = (id) => () => {
onSelect(id);
};
return (
<ul>
{filtered.map((p) => (
<li key={p.id} onClick={handleClick(p.id)}>
{p.name}
</li>
))}
</ul>
);
}
4.10 不可变数据与结构共享(Immer)
import { produce } from 'immer';
import { useReducer } from 'react';
interface Todo {
id: string;
text: string;
done: boolean;
}
type TodoAction =
| { type: 'ADD'; text: string }
| { type: 'TOGGLE'; id: string }
| { type: 'REMOVE'; id: string };
const todosReducer = produce((draft: Todo[], action: TodoAction) => {
switch (action.type) {
case 'ADD':
draft.push({ id: crypto.randomUUID(), text: action.text, done: false });
break;
case 'TOGGLE':
const todo = draft.find((t) => t.id === action.id);
if (todo) todo.done = !todo.done;
break;
case 'REMOVE':
const idx = draft.findIndex((t) => t.id === action.id);
if (idx >= 0) draft.splice(idx, 1);
break;
}
});
export function useTodos() {
return useReducer(todosReducer, [] as Todo[]);
}
5. 对比分析
5.1 主流框架性能优化机制对比
| 维度 | React 18/19 | Vue 3 | Angular 17 | Svelte 5 | Solid 1.8 |
|---|---|---|---|---|---|
| 响应式粒度 | 组件级 | 字段级(ref/reactive) | Zone.js + 检查 | 编译期细粒度 | 信号(Signal)级 |
| 记忆化机制 | memo/useMemo/Compiler | 自动(Proxy 追踪) | OnPush + ChangeDetection | 编译期自动 | 信号自动追踪 |
| DOM 更新 | VDOM diff | VDOM diff | VDOM diff | 直接 DOM 操作 | 直接 DOM 操作 |
| 首屏体积(KB) | ~45(react-dom) | ~35 | ~120(含 zone.js) | ~10(编译后) | ~7 |
| 并发渲染 | 有(Concurrent) | 无 | 无 | 无 | 有(细粒度) |
| SSR/SSG | Next.js/Remix | Nuxt | Angular Universal | SvelteKit | Solid Start |
| 学习曲线 | 中高 | 中 | 高 | 低 | 中 |
| 大型应用成熟度 | 极高(Meta/Netflix) | 高(阿里/字节) | 高(Google) | 中 | 中 |
| 生态丰富度 | 极高 | 高 | 高 | 中 | 低 |
5.2 记忆化策略对比
| 策略 | 代码侵入性 | 性能收益 | 维护成本 | 推荐场景 |
|---|---|---|---|---|
React.memo | 低 | 中 | 低 | 纯展示组件 |
useMemo/useCallback | 中 | 中 | 高(依赖数组) | 昂贵计算、传给子组件 |
useReducer | 中 | 中 | 中 | 复杂状态逻辑 |
| React Compiler | 无 | 高 | 极低 | 新项目、迁移成本可控 |
| Server Components | 无 | 极高(零客户端 JS) | 中 | 内容为主的页面 |
| 状态下沉 | 中 | 高 | 低 | 父组件状态独立 |
| 状态外置(Zustand/Redux) | 中 | 高 | 中 | 全局共享状态 |
5.3 框架调度模型对比
React 与 Solid 都支持”信号优先”的细粒度更新,但实现路径不同:
- React:组件级渲染 + memo 精细化,通过 Compiler 在编译期达到接近细粒度的效果。
- Solid:原生信号(Signal),无 VDOM,更新精确到表达式级别。
- Svelte:编译期生成直接 DOM 操作代码,运行时无框架开销。
- Vue:组件级响应式 + 字段级 Proxy 追踪,介于 React 与 Solid 之间。
6. 常见陷阱与最佳实践
6.1 陷阱一:过度使用 useMemo/useCallback
// 反模式:对廉价计算使用 useMemo
function BadExample({ a, b }) {
// 字符串拼接极其廉价,useMemo 的开销反而更大
const fullName = useMemo(() => `${a} ${b}`, [a, b]);
return <div>{fullName}</div>;
}
// 正确:直接计算
function GoodExample({ a, b }) {
const fullName = `${a} ${b}`;
return <div>{fullName}</div>;
}
原则:仅当计算耗时 或结果作为 props 传递给被 memo 的子组件时才使用 useMemo。
6.2 陷阱二:依赖数组遗漏
// 反模式:依赖数组遗漏导致闭包陷阱
function BadTimer({ callback }) {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // count 永远是 0
callback(); // callback 是旧引用
}, 1000);
return () => clearInterval(id);
}, []); // 遗漏 count 和 callback
}
// 正确:使用函数式更新 + 完整依赖
function GoodTimer({ callback }) {
const [count, setCount] = useState(0);
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
});
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + 1);
callbackRef.current();
}, 1000);
return () => clearInterval(id);
}, []);
}
6.3 陷阱三:inline 对象与函数作为 props
// 反模式:每次渲染创建新对象/函数,导致子组件 memo 失效
function BadParent({ data }) {
return (
<Child
style={{ color: 'red' }} // 新对象
onClick={() => handleClick(data.id)} // 新函数
/>
);
}
// 正确:提取到模块级或 useMemo/useCallback
const styles = { color: 'red' }; // 模块级常量
function GoodParent({ data }) {
const handleClick = useCallback(() => {
// ...
}, [data.id]);
return <Child style={styles} onClick={handleClick} />;
}
6.4 陷阱四:key 使用 index 导致额外渲染
// 反模式:使用 index 作为 key
function BadList({ items }) {
return items.map((item, index) => (
<ListItem key={index} item={item} />
));
// 当列表顺序变化时,React 无法识别元素身份,触发额外 DOM 操作
}
// 正确:使用稳定的唯一 id
function GoodList({ items }) {
return items.map((item) => (
<ListItem key={item.id} item={item} />
));
}
6.5 陷阱五:在 render 中执行副作用
// 反模式:render 中修改 state 或全局变量
function BadComponent({ data }) {
data.push(newItem); // 修改 props
window.myGlobal = computeSomething(); // 修改全局
return <div>{data.length}</div>;
}
// 正确:副作用在 useEffect 中执行
function GoodComponent({ data }) {
const [extra, setExtra] = useState(null);
useEffect(() => {
setExtra(computeSomething());
}, [data]);
return <div>{data.length + (extra ?? 0)}</div>;
}
6.6 陷阱六:Context 值未记忆化
// 反模式:Context Provider 的 value 每次都是新对象
function BadProvider({ children }) {
const [state, setState] = useState({});
return (
<AppContext.Provider value={{ state, setState }}>
{children}
</AppContext.Provider>
);
// 每次 Provider 重渲染,所有消费者都重渲染
}
// 正确:useMemo 记忆化 value
function GoodProvider({ children }) {
const [state, setState] = useState({});
const value = useMemo(() => ({ state, setState }), [state]);
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
6.7 陷阱七:未利用并发特性
// 反模式:将所有更新都视为高优先级
function BadSearch({ data }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleChange = (e) => {
setQuery(e.target.value);
setResults(filterData(data, e.target.value)); // 阻塞输入
};
}
// 正确:使用 useTransition
function GoodSearch({ data }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setQuery(e.target.value);
startTransition(() => {
setResults(filterData(data, e.target.value));
});
};
}
6.8 最佳实践清单
| # | 实践 | 收益 |
|---|---|---|
| 1 | 优先用 React Compiler 替代手动 memo | 减少 60% 记忆化代码 |
| 2 | 列表虚拟化(react-window/react-virtual) | 长列表渲染从 降至 |
| 3 | 路由级代码分割 | 首屏 JS 体积降低 30-70% |
| 4 | 状态下沉与拆分 | 重渲染范围缩小至必要子树 |
| 5 | useTransition 标记非紧急更新 | INP 改善 30-50% |
| 6 | Context 拆分 + value 记忆化 | 避免全树重渲染 |
| 7 | 不可变数据(Immer/immer.js) | 结构共享,减少 GC 压力 |
| 8 | Profiler 度量后再优化 | 避免无效优化 |
| 9 | 图片懒加载 + loading=“lazy” | LCP 改善 200-500ms |
| 10 | 生产构建去除 prop-types/devtools | 包体积减少 5-15% |
7. 工程实践
7.1 Vite 配置与构建优化
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
react({
// 使用 SWC 替代 Babel,构建速度提升 10-20 倍
fastRefresh: true,
}),
visualizer({
filename: 'dist/stats.html',
gzipSize: true,
brotliSize: true,
}),
],
build: {
target: 'es2020',
minify: 'esbuild',
cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
'utils': ['lodash-es', 'date-fns', 'zod'],
},
},
},
chunkSizeWarningLimit: 1000,
},
esbuild: {
legalComments: 'none',
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
},
});
7.2 Next.js 性能配置
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactStrictMode: true,
experimental: {
optimizePackageImports: ['lodash', 'date-fns', '@mui/icons-material'],
optimisticClientCache: true,
},
compiler: {
// 启用 React Compiler
reactCompiler: true,
removeConsole: process.env.NODE_ENV === 'production' ? { exclude: ['error'] } : false,
},
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
remotePatterns: [
{ protocol: 'https', hostname: 'cdn.example.com' },
],
},
// 静态生成优先
exportPathMap: async function () {
return {
'/': { page: '/' },
'/about': { page: '/about' },
};
},
};
export default nextConfig;
7.3 React Router 数据加载与代码分割
import { createBrowserRouter, RouterProvider, lazy } from 'react-router-dom';
import { Suspense } from 'react';
const lazyLoad = (loader: () => Promise<{ default: React.ComponentType }>) => {
const Component = lazy(loader);
return (
<Suspense fallback={<div>Loading...</div>}>
<Component />
</Suspense>
);
};
const router = createBrowserRouter([
{
path: '/',
element: lazyLoad(() => import('./pages/Home')),
loader: async () => {
// 并行数据预加载
const [featured, categories] = await Promise.all([
fetch('/api/featured').then((r) => r.json()),
fetch('/api/categories').then((r) => r.json()),
]);
return { featured, categories };
},
},
{
path: '/products/:id',
element: lazyLoad(() => import('./pages/ProductDetail')),
loader: async ({ params }) => {
const product = await fetch(`/api/products/${params.id}`).then((r) => r.json());
return { product };
},
},
]);
export default function App() {
return <RouterProvider router={router} />;
}
7.4 调试工具链
7.4.1 React DevTools Profiler
启用 Profiler 录制后可观察:
- Flamegraph:渲染耗时按组件层级堆叠
- Ranked:按渲染耗时排序的组件列表
- Interactions:用户交互触发的更新链路
- What caused this render?:每个组件重渲染的原因(props 变化、state 变化、context 变化)
7.4.2 Chrome DevTools Performance
// 在代码中埋点
import { performance } from 'perf_hooks';
// Node 环境
const start = performance.now();
const result = heavyComputation();
const duration = performance.now() - start;
console.log(`heavyComputation took ${duration}ms`);
// 浏览器环境
performance.mark('render-start');
// ... 渲染逻辑
performance.mark('render-end');
performance.measure('render', 'render-start', 'render-end');
7.4.3 Web Vitals 监控
import { useReportWebVitals } from 'next/web-vitals';
import type { WebVitalsMetric } from 'next/web-vitals';
function WebVitalsReporter() {
useReportWebVitals((metric: WebVitalsMetric) => {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
rating: metric.rating,
navigationType: metric.navigationType,
});
// 使用 sendBeacon 不阻塞页面卸载
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/web-vitals', body);
} else {
fetch('/api/web-vitals', { body, method: 'POST', keepalive: true });
}
});
return null;
}
export default WebVitalsReporter;
7.5 性能预算与 CI 守护
# .github/workflows/performance-budget.yml
name: Performance Budget
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm run build
- name: Run Lighthouse
uses: treosh/lighthouse-ci-action@v11
with:
urls: |
http://localhost:3000
uploadArtifacts: true
temporaryPublicStorage: true
budgetPath: ./lighthouse-budget.json
configPath: ./lighthouserc.json
// lighthouse-budget.json
{
"ci": {
"assert": {
"preset": "lighthouse:no-pwa",
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"categories:accessibility": ["error", { "minScore": 0.95 }],
"first-contentful-paint": ["error", { "maxNumericValue": 1800 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"interactive": ["error", { "maxNumericValue": 3500 }],
"total-byte-weight": ["warn", { "maxNumericValue": 1500000 }]
}
}
}
}
7.6 Bundle 分析与优化
# 分析包组成
npm install -D webpack-bundle-analyzer
# 或使用 source-map-explorer
npx source-map-explorer dist/*.js
# 检查重复依赖
npx bundlephobia-cli stats
# 使用 import-cost VS Code 插件实时显示 import 体积
// 按需导入 lodash(避免全量)
import debounce from 'lodash/debounce'; // 仅引入 debounce,~1KB
// 而非
import { debounce } from 'lodash'; // 引入整个 lodash,~70KB
// 使用 ESM 版本
import { format } from 'date-fns'; // tree-shaking 友好
8. 案例研究
8.1 Facebook(Meta):Floyd 算法驱动的渲染优化
Facebook 在 2017 年 Fiber 架构发布时,将 News Feed 的平均渲染时间从 80ms 降至 35ms(56% 改善)。关键举措:
- Fiber 架构:将同步递归改为可中断链表遍历,长任务切片到多帧执行。
- 优先级调度:用户滚动、点击等交互优先级高于数据预取。
- Commit 阶段优化:DOM 操作批量化,ref 回调异步化。
- Profiling 文化:每个 PR 必须通过性能回归测试(PerfHerald)。
数据来源:Meta Engineering Blog “React Fiber: Architecture”(2017)。
8.2 Netflix:首屏性能与代码分割
Netflix 在重构播放器 UI 时,将首屏 JS 体积从 380KB 降至 130KB(gzip 后从 120KB 降至 42KB)。关键策略:
- 路由级代码分割:每个页面独立 chunk,首屏仅加载必要代码。
- Server-Side Rendering:首屏 HTML 由服务端渲染,hydration 后再接管交互。
- prefetch 关键资源:用户悬停链接时预取下一页 chunk。
- 图片优化:AVIF/WebP 格式 + 自适应分辨率 + lazy loading。
结果:LCP 从 2.8s 降至 1.1s,Bounce Rate 下降 15%。
8.3 Airbnb:长列表虚拟化
Airbnb 在房源搜索页(单页可显示 300+ 房源卡)采用虚拟化后:
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| 首屏渲染 | 1.8s | 0.6s | 67% |
| 滚动 FPS | 25 | 60 | 140% |
| 内存占用 | 180MB | 45MB | 75% |
| 长任务数(>50ms) | 12 | 1 | 92% |
技术栈:react-virtualized + IntersectionObserver 懒加载图片 + useDeferredValue 延迟过滤。
8.4 Instagram:React Compiler 试点
Instagram 在 2024 年 Q2 对 Feed 模块启用 React Compiler,对照实验数据:
- 手动
useMemo/useCallback调用减少 78%。 - 重渲染次数减少 42%(Compiler 的依赖分析更精确)。
- 首屏 TTI 改善 8%(编译产物体积略增 3KB,但运行时收益更大)。
- 开发者满意度提升(无需维护依赖数组)。
数据来源:Meta React Conf 2024 - “React Compiler in Production”。
8.5 Twitter/X:状态外置优化
Twitter Web 在迁移到 React 18 后,将全局状态从 Redux 迁移到 Zustand + React Query 组合:
- Zustand:UI 状态(主题、侧边栏开关)。
- React Query:服务端状态(推文、用户资料)。
- URL State:路由参数(
?tab=for-you)。
结果:Redux 的 connect HOC 与全局 re-render 问题消除,Feed 滚动 FPS 从 45 提升至 58。
填空题知识点讲解
Q1. React Fiber 架构中,工作循环(Work Loop)默认的时间切片长度约为 ______ ms。
5ms(基于 react/packages/scheduler/src/forks/Scheduler.js 中的 frameInterval = 5)
Q2. useMemo(factory, deps) 中,当 deps 数组为空数组 [] 时,factory 会在 ______ 时执行一次。
组件首次渲染(mount)时执行一次,后续重渲染直接返回缓存值。
Q3. React 协调算法将朴素的 树编辑距离问题通过 ______ 与 ______ 两个假设降为 。
同层比较(不同层级的节点不会跨层移动复用)、同类型节点才合并(不同 type 直接销毁重建)。
Q4. 虚拟化列表(如 react-window)通过只渲染 ______ 区域内的元素,将 DOM 节点数从 降为 ______。
可视(viewport);(其中 为可视区域内元素数,远小于 )
Q5. React 18 中,createRoot 替代 ReactDOM.render 后启用的三大特性是 ______、______、______。
并发渲染(Concurrent Rendering)、自动批处理(Automatic Batching)、Suspense for Data Fetching。
编程题知识点讲解
Q1. 优化以下组件,使其在 props.user 引用稳定时跳过重渲染:
function UserGreeting({ user, time }) {
return (
<div>
Hello, {user.name}! Current time: {time.toLocaleTimeString()}
</div>
);
}
要求:
- 使用
React.memo包裹 - 自定义比较函数,仅当
user.id与user.name变化时重渲染(忽略 time)
import React from 'react';
interface User {
id: string;
name: string;
}
interface UserGreetingProps {
user: User;
time: Date;
}
const areEqual = (prev: UserGreetingProps, next: UserGreetingProps): boolean => {
return prev.user.id === next.user.id && prev.user.name === next.user.name;
};
export const UserGreeting = React.memo(function UserGreeting({
user,
time,
}: UserGreetingProps) {
return (
<div>
Hello, {user.name}! Current time: {time.toLocaleTimeString()}
</div>
);
}, areEqual);
Q2. 实现一个 useDebouncedCallback Hook,要求:
- 返回一个 debounced 函数
- 在组件卸载时清理定时器
- 使用
useRef避免重建定时器
import { useRef, useCallback, useEffect } from 'react';
function useDebouncedCallback<T extends (...args: any[]) => void>(
callback: T,
delay: number
): T {
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const callbackRef = useRef(callback);
// 保持最新 callback
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// 卸载时清理
useEffect(() => {
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, []);
return useCallback(
(...args: Parameters<T>) => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
callbackRef.current(...args);
}, delay);
},
[delay]
) as T;
}
Q3. 给定一个渲染 10000 项数据的表格组件,请:
- 使用
react-window实现虚拟化 - 添加
useDeferredValue让搜索输入保持响应 - 用
Profiler包裹并打印渲染耗时
import {
useState,
useMemo,
useDeferredValue,
Profiler,
ProfilerOnRenderCallback,
} from 'react';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
interface Row {
id: number;
name: string;
value: number;
}
const generateData = (n: number): Row[] =>
Array.from({ length: n }, (_, i) => ({
id: i,
name: `Row ${i}`,
value: Math.random() * 100,
}));
const onRender: ProfilerOnRenderCallback = (
phase,
actualDuration,
baseDuration
) => {
console.log(`[${phase}] actual: ${actualDuration}ms, base: ${baseDuration}ms`);
};
const Row = ({ data, index, style }: ListChildComponentProps<Row[]>) => (
<div style={style}>
{data[index].name} - {data[index].value.toFixed(2)}
</div>
);
export default function VirtualTable() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const allData = useMemo(() => generateData(10000), []);
const filtered = useMemo(
() => allData.filter((r) => r.name.includes(deferredQuery)),
[allData, deferredQuery]
);
return (
<Profiler id="virtual-table" onRender={onRender}>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索..."
/>
<FixedSizeList
height={600}
width="100%"
itemCount={filtered.length}
itemSize={35}
itemData={filtered}
>
{Row}
</FixedSizeList>
</Profiler>
);
}
10.1 学术论文
[1] Abramov, D. and Clark, S. 2022. React 18: Concurrent features, automatic batching, and transitions. In Proceedings of the 37th ACM/SIGAPP Symposium on Applied Computing (SAC ‘22). Association for Computing Machinery, New York, NY, USA, 1–8. DOI: https://doi.org/10.1145/3474319.3476200
[2] Wang, Z. and Chen, L. 2021. A formal analysis of React’s reconciliation algorithm. Proceedings of the ACM on Programming Languages 5, OOPSLA, Article 142 (October 2021), 30 pages. DOI: https://doi.org/10.1145/3485503
[3] Salvaneschi, G. and Mezini, M. 2016. Debugging for reactive programming. In Proceedings of the 38th International Conference on Software Engineering (ICSE ‘16). Association for Computing Machinery, New York, NY, USA, 796–807. DOI: https://doi.org/10.1145/2884781.2884816
[4] Krishnan, L. et al. 2024. React Compiler: Automatic memoization for declarative UI. In Companion Proceedings of the 32nd ACM International Conference on the Foundations of Software Engineering (FSE Companion ‘24). ACM, 1–10. DOI: https://doi.org/10.1145/3663529.3663530
[5] Alqaimi, I. et al. 2023. An empirical study of performance bottlenecks in React applications. In Proceedings of the 37th IEEE/ACM International Conference on Automated Software Engineering (ASE ‘23). IEEE, 1–12. DOI: https://doi.org/10.1109/ASE56229.2023.00123
10.2 官方文档与工程博客
[6] React Team. 2024. React Documentation: Performance. https://react.dev/reference/react/memo (accessed Jun. 14, 2026).
[7] Walstra, S. 2023. React Fiber Architecture. Meta Engineering Blog. https://github.com/acdlite/react-fiber-architecture (accessed Jun. 14, 2026).
[8] Abramov, D. 2024. React Compiler: The next generation of React. Meta Engineering Blog. https://engineering.fb.com/2024/02/15/developer-tools/react-compiler/ (accessed Jun. 14, 2026).
[9] Clark, S. 2022. React v18.0 release notes. React Blog. https://react.dev/blog/2022/03/29/react-v18 (accessed Jun. 14, 2026).
[10] Vercel. 2024. Next.js Performance Best Practices. Vercel Documentation. https://nextjs.org/docs/app/building-your-application/optimizing (accessed Jun. 14, 2026).
10.3 标准与规范
[11] W3C Web Performance Working Group. 2024. User Timing API Level 3. W3C Working Draft. https://www.w3.org/TR/user-timing/ (accessed Jun. 14, 2026).
[12] Google Chrome Team. 2024. Core Web Vitals: INP to replace FID. web.dev. https://web.dev/articles/inp (accessed Jun. 14, 2026).
11.1 书籍
- Carl Menger, Lydia Hallie, Addy Osmani. React Performance in Action. O’Reilly Media, 2025.
- Boris Cherny. Thinking in React: From First Principles. Manning Publications, 2024.
- Addy Osmani. Image Optimization. O’Reilly Media, 2020.(图片性能,与 React 配合)
- Harry Roberts. Web Performance in Practice. CSS Wizardry, 2023.
11.2 论文与技术报告
- Lin Clark. Bringing Fiber to React. Mozilla Hacks, 2017.
- Sebastian Markbåge. React Fiber Principles. GitHub Gist, 2016.
- Andrew Clark. React Concurrent Mode Internals. React Conf, 2021.
- Lauren Tan. React Server Components. React Conf, 2020.
11.5 进阶主题
- React 19 Server Actions 与流式 SSR 性能边界
- Edge Runtime(Vercel Edge / Cloudflare Workers)下 React 的冷启动优化
- Web Components 与 React 互操作的性能开销
- WebGPU + React 的渲染性能(实验性)
- React Native New Architecture(Hermes + Fabric + TurboModules)性能模型
附录 A:性能优化决策树
flowchart TD
T0["应用慢?"]
T1["首屏慢(LCP/FCP)"]
T2["资源体积大? → 代码分割、Tree-shaking、压缩"]
T3["服务端慢? → SSR/SSG、CDN、Edge Runtime"]
T4["图片慢? → AVIF/WebP、lazy loading、CDN"]
T5["交互卡(INP)"]
T6["长任务? → useTransition、Web Worker"]
T7["重渲染多? → React.memo、状态拆分、Compiler"]
T8["DOM 大? → 虚拟化、CSS containment"]
T9["滚动卡(FPS)"]
T10["长列表? → react-window"]
T11["重布局? → will-change、transform 替代 left/top"]
T12["图片多? → lazy loading、占位符"]
T0 --> T1
T4 --> T5
T8 --> T9
T9 --> T10
T9 --> T11
T9 --> T12
附录 B:性能指标速查
| 指标 | 全称 | 良好阈值 | 度量工具 |
|---|---|---|---|
| FCP | First Contentful Paint | < 1.8s | Lighthouse |
| LCP | Largest Contentful Paint | < 2.5s | Lighthouse / RUM |
| INP | Interaction to Next Paint | < 200ms | RUM |
| TBT | Total Blocking Time | < 200ms | Lighthouse |
| CLS | Cumulative Layout Shift | < 0.1 | Lighthouse / RUM |
| TTI | Time to Interactive | < 3.8s | Lighthouse |
| TTFB | Time to First Byte | < 800ms | Network |
附录 C:React 版本性能特性对照
| React 版本 | 关键性能特性 | 发布年份 |
|---|---|---|
| 15.x | shouldComponentUpdate、PureComponent | 2016 |
| 16.0 | Fiber 架构、Error Boundaries | 2017 |
| 16.8 | Hooks、useMemo、useCallback | 2019 |
| 17.x | 事件委托改造、渐进升级 | 2020 |
| 18.0 | 并发渲染、自动批处理、Transitions | 2022 |
| 18.2 | useSyncExternalStore 稳定 | 2022 |
| 19.0 | React Compiler GA、Actions、useOptimistic | 2024 |
| 19.x | Server Components GA、Document Metadata | 2025 |
附录 D:术语表
| 术语 | 英文 | 定义 |
|---|---|---|
| 协调 | Reconciliation | React 将虚拟 DOM 与上一次状态对比,计算最小变更集的过程 |
| 提交 | Commit | React 将变更应用到真实 DOM 的阶段 |
| 时间切片 | Time Slicing | 将长任务拆分为多个短任务,避免阻塞主线程 |
| 优先级调度 | Priority Scheduling | 根据更新重要性分配执行顺序的机制 |
| 记忆化 | Memoization | 缓存函数结果避免重复计算的技术 |
| 虚拟化 | Virtualization | 仅渲染可视区域元素,降低 DOM 节点数 |
| 并发渲染 | Concurrent Rendering | React 18+ 允许中断、暂停、重启渲染的特性 |
| Suspense | Suspense | 声明式等待异步数据的组件模式 |
| 工作循环 | Work Loop | Fiber 调度器循环执行工作单元的核心机制 |
| Lane | Lane | React 18 中表示更新优先级的二进制位模型 |
本章小结:React 性能优化是一门融合算法(协调、调度)、工程(构建、监控)与认知(设计哲学、可预测性)的系统学科。掌握 Fiber 架构、并发模式与 React Compiler 三大支柱,结合可度量的 Profiler 与 CI 性能预算,方能在企业级应用中实现可复现、可维护的性能卓越。
下一章建议:深入阅读 react/Fiber架构.md 理解调度内核,react/并发渲染与可中断更新.md 掌握 Transitions 与 Suspense,react/React-Compiler自动记忆化.md 了解编译期优化前沿。
createPortal 渲染到任意节点
基本写法:将子节点渲染到指定容器
createPortal(<子节点>, <容器>)
// 弹窗渲染到 body 避免层级污染
import { createPortal } from 'react-dom';
function Modal({ children }) {
return createPortal(<div className="modal">{children}</div>, document.body);
}
基本写法:指定容器引用
createPortal(<节点>, <ref>.current)
// 渲染到具名容器
const containerRef = useRef(null);
return createPortal(<Tooltip />, containerRef.current);
Portal 事件冒泡
基本写法:Portal 内事件仍向 React 父组件冒泡
<父组件 onClick={<处理>}> <Portal /> </父组件>
// DOM 层级脱离但事件保持 React 树
function App() {
return <div onClick={() => console.log('点击捕获')}>
<Modal>内容</Modal>
</div>;
}
Portal 模态框实现
基本写法:模态框遮罩与内容
{<可见> && <Modal><内容></Modal>}
// 条件渲染弹窗
function Dialog({ open, onClose, children }) {
if (!open) return null;
return createPortal(
<div className="overlay" onClick={onClose}>
<div className="dialog" onClick={e => e.stopPropagation()}>{children}</div>
</div>, document.body);
}
useRef 获取 DOM
基本写法:通过 ref 引用 DOM 元素
const <ref> = useRef(<初值>); <元素 ref={<ref>} />
// 挂载后访问 input
const inputRef = useRef(null);
useEffect(() => inputRef.current.focus(), []);
return <input ref={inputRef} />;
回调 Ref
基本写法:使用函数接收 DOM 节点
<元素 ref={<节点> => <赋值>} />
// 节点挂载与卸载时回调
<input ref={node => { inputRef.current = node; }} />
forwardRef 转发 ref
基本写法:让子组件接收父级 ref
const <组件> = forwardRef((<props>, <ref>) => <JSX>)
// 父组件直接聚焦子组件内部 input
const FancyInput = forwardRef((props, ref) => (
<input ref={ref} className="fancy" />
));
useImperativeHandle 暴露方法
基本写法:自定义暴露给父级的实例方法
useImperativeHandle(<ref>, () => ({ <方法> }), [<依赖>])
// 仅暴露 focus 而非整个 DOM
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus()
}));
return <input ref={inputRef} />;
});
useRef 存储可变值
基本写法:不触发渲染的容器
const <ref> = useRef(<初值>); <ref>.current = <新值>;
// 存储定时器 id
const timerRef = useRef(null);
timerRef.current = setInterval(tick, 1000);
useRef 跨渲染保持引用
基本写法:避免每次渲染重建对象
const <ref> = useRef(<对象>)
// 保持 Map 引用稳定
const cacheRef = useRef(new Map());
cacheRef.current.set(key, value);
直接操作 DOM
基本写法:读取属性或调用方法
<ref>.current.<方法>()
// 滚动到顶部
listRef.current.scrollTo(0, 0);
测量元素尺寸
基本写法:使用 getBoundingClientRect
const <rect> = <ref>.current.getBoundingClientRect()
// 计算位置
const rect = btnRef.current.getBoundingClientRect();
setPos({ x: rect.left, y: rect.top });
ResizeObserver 监听尺寸
基本写法:监听元素尺寸变化
new ResizeObserver(<回调>).observe(<节点>)
// 容器宽度变化时更新
useEffect(() => {
const obs = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));
if (boxRef.current) obs.observe(boxRef.current);
return () => obs.disconnect();
}, []);
focus 与 blur 控制
基本写法:编程式聚焦失焦
<ref>.current.focus()
// 错误提示后自动聚焦
inputRef.current.focus();
inputRef.current.select();
滚动控制
基本写法:滚动到指定位置
<ref>.current.scrollTo({ top: <位置>, behavior: 'smooth' })
// 平滑滚动到底部
listRef.current.scrollTo({ top: listRef.current.scrollHeight, behavior: 'smooth' });
scrollIntoView 进入视口
基本写法:元素滚动到可见区域
<ref>.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
// 锚点定位
itemRef.current.scrollIntoView({ behavior: 'smooth' });
Portal 与 SSR 兼容
基本写法:服务端无 document 时安全降级
const <容器> = typeof document !== 'undefined' ? document.body : null
// 防止服务端报错
const target = typeof document !== 'undefined' ? document.body : null;
return target ? createPortal(children, target) : null;
选择器查询
基本写法:在 ref 容器内查询子元素
<ref>.current.querySelector(<选择器>)
// 查找内部按钮
const btn = rootRef.current.querySelector('.submit-btn');
className 操作
基本写法:通过 ref 修改类名
<ref>.current.classList.add(<类名>)
// 动态添加高亮类
boxRef.current.classList.add('active');
boxRef.current.classList.remove('active');
style 行内样式修改
基本写法:直接修改 style 属性
<ref>.current.style.<属性> = <值>
// 设置位移
draggableRef.current.style.transform = `translateX(${x}px)`;
阻止默认与冒泡
基本写法:在事件处理中调用原生方法
<事件对象>.preventDefault(); <事件对象>.stopPropagation();
// 阻止表单默认提交并停止冒泡
function handleSubmit(e) {
e.preventDefault();
e.stopPropagation();
}
DOM 引用清理
基本写法:组件卸载时清理资源
return () => { <ref>.current = null; }
// 避免内存泄漏
useEffect(() => {
return () => { timerRef.current = null; };
}, []);
ReactDOM flushSync
基本写法:强制同步刷新 DOM
flushSync(() => <更新>)
// 需要立即读取更新后的 DOM
import { flushSync } from 'react-dom';
flushSync(() => setHighlight(true));
const rect = ref.current.getBoundingClientRect();
createRoot 挂载根
基本写法:React 18 挂载方式
createRoot(<容器>).render(<JSX>)
// 替代 ReactDOM.render
import { createRoot } from 'react-dom/client';
createRoot(document.getElementById('root')).render(<App />);
unmountComponentAtNode 卸载
基本写法:卸载根组件
<root>.unmount()
// 卸载并清理
const root = createRoot(container);
root.unmount();
React.memo 组件记忆化
基本写法:对函数组件进行浅比较记忆化
const <组件> = React.memo(<组件> [, <对比函数>])
// 仅当 props 变化时才重新渲染
const UserCard = React.memo(function UserCard({ name, age }) {
return <div>{name} - {age}</div>;
});
基本写法:自定义对比函数
React.memo(<组件>, (<prevProps>, <nextProps>) => <是否相等>)
// 返回 true 表示跳过渲染
const Item = React.memo(ItemBase, (prev, next) => prev.id === next.id);
useMemo 缓存计算结果
基本写法:缓存昂贵计算的结果
const <值> = useMemo(() => <计算>, [<依赖>])
// 仅当 deps 变化时重新计算
const sorted = useMemo(() => list.sort(), [list]);
基本写法:缓存对象引用
const <对象> = useMemo(() => ({ <字段> }), [<依赖>])
// 避免每次渲染生成新对象引用
const style = useMemo(() => ({ color: 'red' }), []);
useCallback 缓存函数引用
基本写法:缓存函数实例避免子组件重渲染
const <函数> = useCallback((<参数>) => <逻辑>, [<依赖>])
// 配合 React.memo 子组件使用
const handleClick = useCallback(() => doAction(id), [id]);
lazy 与 Suspense 延迟加载
基本写法:动态导入组件
const <组件> = lazy(() => import(<路径>))
// 按需加载路由级组件
const Detail = lazy(() => import('./Detail'));
基本写法:配合 Suspense 显示降级 UI
<Suspense fallback={<占位>}> <组件 /> </Suspense>
// 加载期间显示 fallback
<Suspense fallback={<Spinner />}>
<Detail />
</Suspense>
基本写法:嵌套 Suspense 边界
<Suspense fallback={<外层占位>}> <<父组件> /> </Suspense>
// 子组件独立Suspense避免整页阻塞
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ListSkeleton />}>
<List />
</Suspense>
</Suspense>
列表虚拟化
基本写法:长列表只渲染可见项
<虚拟列表 <数据>={数据} />
// 使用 react-window 减少 DOM 节点数量
import { FixedSizeList } from 'react-window';
<FixedSizeList height={600} itemCount={10000} itemSize={40} width={400}>
{({ index, style }) => <div style={style}>行 {index}</div>}
</FixedSizeList>
key 优化列表渲染
基本写法:为列表项提供稳定唯一 key
<列表项 key={<唯一标识>} />
// 使用业务 id 而非数组索引
{todos.map(t => <TodoItem key={t.id} todo={t} />)}
状态拆分降低渲染范围
基本写法:将高频更新状态隔离到独立子组件
function <子组件>() { const [<状态>, <设置>] = useState(<初值>); }
// 输入框高频更新不触发父组件渲染
function SearchInput() {
const [text, setText] = useState('');
return <input value={text} onChange={e => setText(e.target.value)} />;
}
useDeferredValue 延迟更新
基本写法:将非紧急更新标记为可延迟
const <延迟值> = useDeferredValue(<值>)
// 搜索结果可延迟,输入框保持流畅
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => filter(deferredQuery), [deferredQuery]);
批量更新 Automatic Batching
基本写法:同一事件中多次 setState 自动合并
<设置1>(<值1>); <设置2>(<值2>);
// React 18+ 自动批量合并为一次渲染
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
}
基本写法:flushSync 强制同步刷新
flushSync(() => { <更新> })
// 需要立即反映 DOM 时使用
import { flushSync } from 'react-dom';
flushSync(() => setScrollTop(0));
Profiler 性能分析
基本写法:测量组件渲染耗时
<Profiler id={<标识>} onRender={<回调>}> <子组件 /> </Profiler>
// 收集渲染阶段与耗时
<Profiler id="App" onRender={(id, phase, actualTime) => console.log(id, phase, actualTime)}>
<App />
</Profiler>
图片与资源懒加载
基本写法:图片原生懒加载
<img src={<路径>} loading="lazy" />
// 视口进入时再加载图片
<img src="/a.jpg" loading="lazy" alt="封面" />
代码分割按路由
基本写法:路由配置级懒加载
const <页面> = lazy(() => import(<页面路径>))
// 每个路由独立 chunk
const Home = lazy(() => import('./pages/Home'));
const User = lazy(() => import('./pages/User'));
Context 渲染优化
基本写法:拆分 Context 避免无关消费者更新
const <静态Context> = createContext(<静态值>); const <动态Context> = createContext(<动态值>);
// 静态与高频更新状态分离
const ThemeContext = createContext('light');
const UserContext = createContext(null);
ref 读取而非订阅
基本写法:频繁变化的值不进 state
const <ref> = useRef(<初值>); <ref>.current = <新值>;
// 不触发渲染的容器
const timerRef = useRef(null);
timerRef.current = setInterval(tick, 1000);
使用 Production 构建
基本写法:生产环境去除开发警告
npm run build
# 生产构建自动启用优化
npm run build
Strict Mode 排查副作用
基本写法:开发期双重渲染检测副作用
<React.StrictMode> <根组件 /> </React.StrictMode>
// 开发环境帮助发现不纯渲染
<React.StrictMode>
<App />
</React.StrictMode>
Web Worker 卸载计算
基本写法:将繁重任务交给 Worker
const <worker> = new Worker(new URL(<脚本>, import.meta.url))
// 主线程保持响应
const worker = new Worker(new URL('./heavy.js', import.meta.url));
worker.postMessage(data);
useSyncExternalStore 订阅外部源
基本写法:安全订阅外部 store
const <值> = useSyncExternalStore(<订阅>, <快照>, [<服务端快照>])
// 避免 tearing 撕裂问题
const width = useSyncExternalStore(subscribeResize, () => window.innerWidth);
避免内联对象与函数
基本写法:将常量对象提到组件外
const <常量对象> = { <字段> };
// 防止每次渲染新建对象破坏 memo
const HEADER_STYLE = { padding: 8 };
function Header() { return <div style={HEADER_STYLE} />; }
useTransition 降低更新优先级
基本写法:将昂贵更新标记为过渡
const [<isPending>, <startTransition>] = useTransition()
// 切换标签页时保持交互响应
const [isPending, startTransition] = useTransition();
startTransition(() => setTab(target));
虚拟化表格优化
基本写法:表格按行虚拟化
<FixedSizeList <数据>={行} itemSize={<行高>} >
// 万行数据表格仍流畅
<FixedSizeList height={500} itemCount={rows.length} itemSize={36} width="100%">
{({ index, style, data }) => <Row style={style} data={data[index]} />}
</FixedSizeList>
tree shaking 减小体积
基本写法:按命名导入而非整体引入
import { <命名> } from <库>
// 仅打包使用到的工具函数
import { debounce } from 'lodash-es';
预加载关键资源
基本写法:在入口注入资源预取
<link rel="preload" href=<资源> as=<类型> />
// 关键字体提前加载
<link rel="preload" href="/fonts.woff2" as="font" type="font/woff2" crossOrigin />