运行时优化
减少不必要的组件重渲染与昂贵计算。构建侧见 build。
核心概念
React 的重渲染规则很简单:父组件重渲染,默认所有子组件都会重渲染(不管 props 是否变化),除非子组件被 memo 包裹且 props 浅比较相等。性能优化的本质是在这个默认行为上做「减法」:减少触发渲染的次数、减少每次渲染需要重新计算的内容、减少需要渲染的 DOM 节点数。
避免不必要的重渲染
| 手段 | 说明 |
|---|---|
| 状态下沉 / 提升 | 把 state 放在真正需要的子树根部 |
| Context 拆分 | 高频与低频数据分 Context |
memo + 稳定 props | 见 memo Hook |
| 选择器订阅 | react-redux / Zustand 细粒度选取 state |
| children 作为组件而非渲染函数 | 避免每次渲染都创建新的内联组件定义 |
示例:状态下沉
// 不佳:count 变化会连带整个 Page 重渲染,包括不相关的 Header/Footer
function Page() {
const [count, setCount] = useState(0);
return (
<div>
<Header />
<button onClick={() => setCount((c) => c + 1)}>{count}</button>
<Footer />
</div>
);
}
// 更好:把 count 状态与它的 UI 收敛到独立组件
function Page() {
return (
<div>
<Header />
<Counter />
<Footer />
</div>
);
}
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}
Header、Footer 不再因为 count 变化而重渲染——它们甚至不知道 count 的存在。
示例:useCallback 配合 memo
const ExpensiveRow = memo(function ExpensiveRow({
item,
onSelect,
}: { item: Item; onSelect: (id: string) => void }) {
console.log("render row", item.id);
return <li onClick={() => onSelect(item.id)}>{item.name}</li>;
});
function List({ items }: { items: Item[] }) {
const [selectedId, setSelectedId] = useState<string | null>(null);
// 若不用 useCallback,每次 List 渲染都会创建新函数引用,
// 使 memo 的浅比较失效,ExpensiveRow 依然会重渲染
const handleSelect = useCallback((id: string) => setSelectedId(id), []);
return (
<ul>
{items.map((item) => (
<ExpensiveRow key={item.id} item={item} onSelect={handleSelect} />
))}
</ul>
);
}
useCallback/useMemo 本身不是「万能加速器」,只有配合 memo 让下游组件真正跳过渲染时才有意义;滥用反而增加代码复杂度和一点点内存/比较开销。
列表与虚拟滚动
长列表(数千行)应使用虚拟滚动(只渲染可视区域 DOM),例如:
- TanStack Virtual
react-window
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 48,
});
return (
<div ref={parentRef} style={{ height: 480, overflow: "auto" }}>
<div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
{virtualizer.getVirtualItems().map((row) => (
<div
key={row.key}
style={{ position: "absolute", top: row.start, height: row.size, width: "100%" }}
>
{items[row.index].name}
</div>
))}
</div>
</div>
);
}
避免一次性渲染全部节点;DOM 节点数从「数据总量」降为「可视区域数量」,滚动性能不再随数据量线性下降。
昂贵计算
- 用
useMemo缓存过滤/排序结果。 - 用
startTransition降低筛选等更新的优先级(见 渲染)。 - Web Worker 处理重 CPU 任务(与 React 状态更新分离):
// worker.ts
self.onmessage = (e) => {
const result = heavyCompute(e.data);
self.postMessage(result);
};
function useHeavyCompute(input: Input) {
const [result, setResult] = useState<Result | null>(null);
useEffect(() => {
const worker = new Worker(new URL("./worker.ts", import.meta.url));
worker.postMessage(input);
worker.onmessage = (e) => setResult(e.data);
return () => worker.terminate();
}, [input]);
return result;
}
开发期分析
- React DevTools Profiler:录制交互,查看组件渲染次数与耗时,定位「哪个组件渲染了但其实不需要」。
- 结合浏览器 Performance 面板定位长任务(Long Task),尤其是主线程被单次渲染占用超过 50ms 的情况。
最佳实践
- 先用 Profiler 测量再优化,不要凭直觉给所有组件加
memo(memo本身有比较开销,对频繁 props 变化的组件反而无益)。 - 内联传给子组件的对象/数组/函数 props(如
style={{ color: 'red' }})每次渲染都是新引用,会让memo失效,需要useMemo/useCallback或提到组件外部。 - 大列表虚拟化优先于「优化每一行渲染速度」——本质问题往往是渲染节点太多,不是节点渲染太慢。
- 高频更新(拖拽、动画进度)尽量脱离 React 状态循环,用 ref 直接操作 DOM 或搭配 CSS/Web Animations API。
- 避免过早抽象「性能优化 Hook」,多数中后台系统的瓶颈是网络请求而非渲染,先用 Profiler 确认瓶颈位置。
常见坑
| 现象 | 常见原因 | 处理 |
|---|---|---|
加了 memo 组件还是每次都重渲染 | 传入了每次都新建的对象/函数 props | 配合 useMemo/useCallback 稳定引用 |
| 列表滚动卡顿 | 未虚拟化、每行组件较重 | 引入 TanStack Virtual / react-window |
| 输入框卡顿 | 每次按键都触发昂贵的同步计算 | 用 startTransition 降低非紧急更新优先级 |
| Context 消费方全部重渲染 | 单一大 Context 塞了高频与低频数据 | 拆分多个 Context,或引入选择器式状态库 |
useMemo/useCallback 用了却没效果 | 依赖数组不稳定,或下游组件本身没做 memo | 检查依赖数组与子组件是否配合 memo |
延伸阅读
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| React:性能优化 | 官方建议 |
| React DevTools | 开发者工具 |
| web.dev:Optimize INP | 交互延迟(英文) |
| TanStack Virtual | 虚拟滚动 |
| React:memo | API |
相关文章
构建与分包
通过代码分割与构建配置减小首屏 JavaScript 体积。涵盖 React.lazy/Suspense、路由级分割、Tree Shaking、Vite 生产构建、体积分析、最佳实践与常见坑。运行时优化见 runtime。
运行时优化
在 变更检测 策略正确的前提下,减少模板计算与 DOM 规模。涵盖 OnPush、@for track、纯管道、虚拟滚动、懒加载、构建体积分析与常见性能陷阱。
运行时优化
减少不必要的渲染与响应式开销。构建侧见 build。
渲染与协调
React 将组件渲染为虚拟 DOM 树,再协调(reconciliation)到浏览器 DOM。理解该过程有助于解释 key、memo 与性能优化。涵盖渲染流程、Fiber、startTransition、Suspense、最佳实践与常见坑。
工程化
新建 React 项目时,官方与社区主流工具为 Vite;全栈场景常用 Next.js。涵盖 Vite + React、Create React App 现状、Next.js、目录结构、环境变量、部署、最佳实践与常见坑。
TypeScript
React 官方推荐 TypeScript。类型主要落在 Props、事件、Ref 与 Hooks 返回值上。见 组件。
Series
performance
2 / 2