React 性能优化
大约 11 分钟约 3175 字
React 性能优化
简介
React 性能优化涵盖重渲染控制、组件懒加载、虚拟化列表、状态管理策略和并发特性。理解 React 的渲染机制(默认父组件更新时所有子组件都重渲染)和优化手段,有助于构建流畅的大型应用。React 的优化核心思想是"先测量再优化"——使用 React DevTools Profiler 定位性能瓶颈,然后针对性地使用 memo、useMemo、useCallback、虚拟化和代码分割。过早优化不仅浪费开发时间,还可能增加代码复杂度。
特点
实现
重渲染控制
import { memo, useMemo, useCallback, useState, Profiler } from 'react';
// ========== React.memo — 浅比较 props ==========
// 只有 props 变化时才重渲染(浅比较)
const ExpensiveList = memo<{
items: Item[];
onSelect: (id: string) => void;
title: string;
}>(
({ items, onSelect, title }) => {
console.log('ExpensiveList rendered');
return (
<div>
<h3>{title}</h3>
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onSelect(item.id)}>
{item.name}
</li>
))}
</ul>
</div>
);
},
// 自定义比较函数(可选)
(prevProps, nextProps) => {
return (
prevProps.items === nextProps.items &&
prevProps.onSelect === nextProps.onSelect &&
prevProps.title === nextProps.title
);
}
);
// ========== useMemo — 缓存计算结果 ==========
// 只在依赖变化时重新计算,避免每次渲染都执行昂贵的计算
function Dashboard({ data }: { data: RawData[] }) {
// 不好的做法 — 每次渲染都计算
// const processed = data.filter(d => d.active).map(d => expensiveCalc(d));
// 好的做法 — 只在 data 变化时计算
const processed = useMemo(() => {
console.log('Recalculating processed data');
return data
.filter(d => d.active)
.map(d => ({ ...d, score: calculateScore(d) }))
.sort((a, b) => b.score - a.score);
}, [data]); // 依赖数组 — 只在 data 变化时重新计算
return <Chart data={processed} />;
}
// 注意:useMemo 本身有开销,简单计算不需要 memoize
// 以下情况不需要 useMemo:
// - 基本类型计算(字符串拼接、简单数学运算)
// - 依赖每次渲染都变化的值
// - 计算速度极快(< 0.1ms)
// ========== useCallback — 缓存回调函数 ==========
// 保持函数引用稳定,防止传递给 memo 子组件时触发重渲染
function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 不好的做法 — 每次渲染创建新函数
// const handleClick = (id: string) => { setCount(c => c + 1); };
// 好的做法 — useCallback 保持引用稳定
const handleClick = useCallback((id: string) => {
setCount(c => c + 1);
}, []); // 空依赖 — 函数引用永远不变
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
}, []);
return (
<div>
<input value={text} onChange={handleChange} />
<p>Count: {count}</p>
<ExpensiveList items={items} onSelect={handleClick} title="设备列表" />
</div>
);
}
// ========== React Profiler — 性能测量 ==========
function onRenderCallback(
id: string, // 组件标识
phase: 'mount' | 'update' | 'nested-update',
actualDuration: number, // 本次渲染耗时
baseDuration: number, // 不使用 memo 的预估耗时
startTime: number,
commitTime: number,
) {
console.log(`${id} (${phase}): ${actualDuration}ms`);
}
function App() {
return (
<Profiler id="Dashboard" onRender={onRenderCallback}>
<Dashboard data={data} />
</Profiler>
);
}代码分割与懒加载
import { lazy, Suspense } from 'react';
// ========== 路由级懒加载 ==========
// 使用 React.lazy 和 Suspense 实现代码分割
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const UserProfile = lazy(() => import('./pages/UserProfile'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
// 自定义 Loading 组件
function LoadingFallback() {
return (
<div className="loading-container">
<div className="spinner" />
<p>页面加载中...</p>
</div>
);
}
function App() {
return (
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Dashboard />} />
<Route path="settings" element={<Settings />} />
<Route path="profile/:id" element={<UserProfile />} />
<Route path="admin" element={<AdminPanel />} />
</Route>
</Routes>
</Suspense>
);
}
// ========== 组件级懒加载 ==========
// 非路由组件也可以懒加载
const HeavyChart = lazy(() => import('./components/HeavyChart'));
const RichTextEditor = lazy(() => import('./components/RichTextEditor'));
function EditorPage() {
const [showEditor, setShowEditor] = useState(false);
return (
<div>
<button onClick={() => setShowEditor(true)}>打开编辑器</button>
{showEditor && (
<Suspense fallback={<div>编辑器加载中...</div>}>
<RichTextEditor />
</Suspense>
)}
</div>
);
}
// ========== 预加载 ==========
// 在用户可能导航到某个页面前预加载
function Navbar() {
const handleMouseEnter = () => {
// 鼠标悬停时预加载
import('./pages/AdminPanel');
};
return (
<nav>
<Link to="/admin" onMouseEnter={handleMouseEnter}>
管理后台
</Link>
</nav>
);
}虚拟化列表
import { FixedSizeList, VariableSizeList } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
// ========== FixedSizeList — 固定高度行 ==========
function BigList({ items }: { items: Item[] }) {
// 每行渲染函数
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
<div style={style} className="list-row">
<ItemCard item={items[index]} />
</div>
);
return (
<div style={{ height: '600px', width: '100%' }}>
<AutoSizer>
{({ width }) => (
<FixedSizeList
height={600}
width={width}
itemCount={items.length}
itemSize={80} // 每行固定高度
overscanCount={5} // 额外渲染的行数(缓冲)
>
{Row}
</FixedSizeList>
)}
</AutoSizer>
</div>
);
}
// ========== VariableSizeList — 可变高度行 ==========
function VariableList({ items }: { items: Message[] }) {
const getItemSize = (index: number) => {
// 根据内容动态计算行高
const item = items[index];
const lines = Math.ceil(item.text.length / 50);
return Math.max(60, 30 + lines * 20);
};
return (
<div style={{ height: '600px', width: '100%' }}>
<AutoSizer>
{({ width }) => (
<VariableSizeList
height={600}
width={width}
itemCount={items.length}
itemSize={getItemSize}
overscanCount={5}
>
{({ index, style }) => (
<div style={style}>
<MessageBubble message={items[index]} />
</div>
)}
</VariableSizeList>
)}
</AutoSizer>
</div>
);
}
// ========== 何时使用虚拟化 ==========
// 数据量 > 500 条,且每项 UI 不简单(包含图片、复杂布局)
// 数据量 > 5000 条,即使每项简单也应虚拟化
// 列表项高度固定 → FixedSizeList(性能更好)
// 列表项高度可变 → VariableSizeList状态优化策略
// ========== 状态下放 ==========
// Bad: 父组件状态变化导致所有子组件重渲染
function BadParent() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
return (
<div>
<SearchInput value={searchTerm} onChange={setSearchTerm} />
<SearchResults results={results} /> {/* 父组件每次输入都重渲染 */}
<StatisticsPanel /> {/* 不需要 searchTerm,但也重渲染了 */}
</div>
);
}
// Good: 将搜索状态下放到需要的组件
function GoodParent() {
return (
<div>
<SearchSection /> {/* 内部管理搜索状态 */}
<StatisticsPanel /> {/* 不受搜索影响 */}
</div>
);
}
function SearchSection() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
return (
<div>
<SearchInput value={searchTerm} onChange={setSearchTerm} />
<SearchResults results={results} />
</div>
);
}
// ========== useReducer 替代多个 useState ==========
// 当状态逻辑复杂或多个状态相互依赖时
function useForm(initialValues: FormValues) {
const [state, dispatch] = useReducer(formReducer, {
values: initialValues,
errors: {},
touched: {},
isSubmitting: false,
});
const setFieldValue = useCallback((name: string, value: any) => {
dispatch({ type: 'SET_FIELD', name, value });
}, []);
const submit = useCallback(async (onSubmit: (values: FormValues) => Promise<void>) => {
dispatch({ type: 'SUBMIT_START' });
try {
await onSubmit(state.values);
dispatch({ type: 'SUBMIT_SUCCESS' });
} catch (error) {
dispatch({ type: 'SUBMIT_ERROR', error: error as Error });
}
}, [state.values]);
return { ...state, setFieldValue, submit };
}
// ========== useSyncExternalStore — 订阅外部数据源 ==========
import { useSyncExternalStore } from 'react';
function useWindowSize() {
const width = useSyncExternalStore(
// subscribe — 订阅变化
(callback) => {
window.addEventListener('resize', callback);
return () => window.removeEventListener('resize', callback);
},
// getSnapshot — 获取当前值(必须返回相同引用避免重渲染)
() => window.innerWidth,
// getServerSnapshot — SSR 时的值
() => 1024,
);
const height = useSyncExternalStore(
(callback) => {
window.addEventListener('resize', callback);
return () => window.removeEventListener('resize', callback);
},
() => window.innerHeight,
() => 768,
);
return { width, height };
}
// ========== useTransition — 低优先级状态更新 ==========
function SearchPage() {
const [isPending, startTransition] = useTransition();
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState<Result[]>([]);
const handleSearch = (term: string) => {
setSearchTerm(term); // 立即更新输入框(高优先级)
startTransition(() => {
// 搜索计算为低优先级,不阻塞输入
const filtered = expensiveSearch(term);
setResults(filtered);
});
};
return (
<div>
<input
value={searchTerm}
onChange={(e) => handleSearch(e.target.value)}
/>
{isPending && <div>搜索中...</div>}
<ResultList results={results} />
</div>
);
}
// ========== useDeferredValue — 延迟更新 ==========
function FilteredList({ filter }: { filter: string }) {
const deferredFilter = useDeferredValue(filter);
const filteredList = useMemo(
() => items.filter(item => item.name.includes(deferredFilter)),
[deferredFilter]
);
return (
<ul>
{filteredList.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}优点
缺点
总结
React 性能优化先测量再优化。使用 React DevTools Profiler 定位重渲染热点。memo 针对子组件 props 不变时避免重渲染,useMemo 缓存昂贵计算,useCallback 缓存回调引用。代码分割(lazy/Suspense)和虚拟化(react-window)解决体积和大数据问题。useTransition 处理低优先级更新避免阻塞 UI。
关键知识点
- React 默认策略是"总是重渲染所有子组件"。
- memo 做浅比较,复杂对象需要自定义 areEqual。
- useMemo 和 useCallback 本身有开销,不要滥用。
- 虚拟化列表只渲染可见区域,适用于大数据但 UI 结构简单的场景。
- useTransition 将状态更新标记为低优先级。
项目落地视角
- 使用 React DevTools Profiler 找到性能瓶颈再优化。
- 大列表(>500 项)必须使用虚拟化。
- 路由级组件使用 lazy 懒加载。
- 建立性能基线,每次发布对比关键指标。
常见误区
- 所有组件都包 memo,没有测量是否有必要。
- useMemo 缓存简单计算(如字符串拼接),开销大于收益。
- 虚拟化列表中每项渲染复杂组件,反而更慢。
- 忘记 useCallback 的依赖数组导致闭包过期。
进阶路线
- 学习 React Concurrent Features(useTransition、useDeferredValue)。
- 研究 React 编译器(React Compiler)的自动优化。
- 了解 Streaming SSR 和 Selective Hydration。
- 研究 React Server Components 的性能优势。
适用场景
- 列表和表格渲染卡顿。
- 首屏加载时间过长。
- 频繁更新的仪表盘。
- 搜索输入卡顿。
落地建议
- 先用 Profiler 测量,再针对性优化。
- 建立性能基线,每次发布对比关键指标。
- 为关键页面设置 Lighthouse CI 检查。
排错清单
- 用 React DevTools 高亮重渲染组件。
- 检查 memo 的 props 是否有稳定引用。
- 确认虚拟化列表的 itemSize 设置正确。
- 检查 useCallback/useMemo 依赖是否正确。
复盘问题
- 你的应用中重渲染最频繁的组件是什么?
- 代码分割后首屏体积减少了多少?
- memo 的使用是否比手动 shouldComponentUpdate 更高效?
- 哪些页面需要虚拟化列表?
性能测量工具
// ========== Web Vitals 集成 ==========
// npm install web-vitals
import { onLCP, onFID, onCLS, onTTFB, onINP } from 'web-vitals';
function reportWebVitals(metric) {
const { name, value, rating, delta, navigationType } = metric;
console.log(`${name}: ${value}ms (${rating})`);
// 上报到分析服务
if (navigator.sendBeacon) {
const body = JSON.stringify({
name,
value: Math.round(value),
rating,
delta,
navigationType,
url: location.href,
timestamp: Date.now(),
});
navigator.sendBeacon('/api/vitals', body);
}
}
// 注册所有 Web Vitals 指标
onLCP(reportWebVitals); // Largest Contentful Paint
onFID(reportWebVitals); // First Input Delay
onCLS(reportWebVitals); // Cumulative Layout Shift
onTTFB(reportWebVitals); // Time to First Byte
onINP(reportWebVitals); // Interaction to Next Paint自定义性能 Hook
// ========== useRenderCount — 检测组件渲染次数 ==========
function useRenderCount(componentName?: string) {
const renderCount = useRef(0);
renderCount.current++;
useEffect(() => {
if (renderCount.current > 1 && componentName) {
console.log(`[Render] ${componentName}: 第 ${renderCount.current} 次渲染`);
}
});
return renderCount.current;
}
// ========== useWhyDidYouRender — 检测重渲染原因 ==========
function useWhyDidYouRender(componentName: string, props: Record<string, unknown>) {
const previousProps = useRef(props);
useEffect(() => {
const allKeys = Object.keys({ ...previousProps.current, ...props });
const changedProps: Record<string, { from: unknown; to: unknown }> = {};
allKeys.forEach((key) => {
if (previousProps.current[key] !== props[key]) {
changedProps[key] = {
from: previousProps.current[key],
to: props[key],
};
}
});
if (Object.keys(changedProps).length > 0) {
console.log(`[WhyDidYouRender] ${componentName}`, changedProps);
}
previousProps.current = props;
});
}
// 使用
function UserProfile({ userId, name, avatar }: UserProfileProps) {
useRenderCount('UserProfile');
useWhyDidYouRender('UserProfile', { userId, name, avatar });
// ...
}React Server Components 性能策略
// ========== RSC 数据获取模式 ==========
// Server Component — 在服务端获取数据,减少客户端 JS 体积
// app/dashboard/page.tsx (Next.js App Router)
// Server Component — 无客户端 JS 开销
async function DashboardPage() {
// 直接在服务端获取数据,无瀑布式请求
const [users, orders, stats] = await Promise.all([
fetchUsers(),
fetchOrders(),
fetchStats(),
]);
return (
<div>
<StatsCard data={stats} /> {/* 静态渲染 */}
<UserList users={users} /> {/* 静态渲染 */}
<InteractiveOrderFilter orders={orders} /> {/* 客户端交互 */}
</div>
);
}
// Client Component — 只在需要交互时使用
'use client';
function InteractiveOrderFilter({ orders }: { orders: Order[] }) {
const [filter, setFilter] = useState('');
const [status, setStatus] = useState('all');
const filteredOrders = useMemo(() => {
return orders.filter(order =>
order.name.includes(filter) &&
(status === 'all' || order.status === status)
);
}, [orders, filter, status]);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<select value={status} onChange={(e) => setStatus(e.target.value)}>
<option value="all">全部</option>
<option value="pending">待处理</option>
<option value="completed">已完成</option>
</select>
<OrderTable orders={filteredOrders} />
</div>
);
}图片与资源优化
// ========== 图片懒加载与优化 ==========
import { useState, useEffect, useRef } from 'react';
// 懒加载图片组件
function LazyImage({ src, alt, width, height, placeholder }: ImageProps) {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ rootMargin: '200px' } // 提前 200px 开始加载
);
if (imgRef.current) observer.observe(imgRef.current);
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef} style={{ width, height }}>
{isInView && (
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
onLoad={() => setIsLoaded(true)}
style={{
opacity: isLoaded ? 1 : 0,
transition: 'opacity 0.3s ease',
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
)}
{!isLoaded && (
<div
style={{
width: '100%',
height: '100%',
background: placeholder || '#f0f0f0',
}}
/>
)}
</div>
);
}
// 使用 next/image(Next.js 优化)
// import Image from 'next/image';
// <Image
// src="/hero.jpg"
// alt="Hero"
// width={1200}
// height={600}
// priority // 首屏图片优先加载
// placeholder="blur"
// />性能优化检查清单
// ========== 性能优化检查清单 ==========
// 以下是一个 React 项目性能审计的完整清单
interface PerformanceChecklist {
// 渲染优化
renderOptimization: {
memoForExpensiveComponents: boolean; // 昂贵组件使用 React.memo
useMemoForExpensiveCalc: boolean; // 昂贵计算使用 useMemo
useCallbackForHandlers: boolean; // 事件处理器使用 useCallback
stateColocation: boolean; // 状态就近放置
noUnnecessaryState: boolean; // 移除不必要的状态
};
// 代码分割
codeSplitting: {
routeLevelLazyLoading: boolean; // 路由级懒加载
componentLevelLazyLoading: boolean; // 大组件懒加载
dynamicImports: boolean; // 动态导入第三方库
};
// 数据获取
dataFetching: {
noWaterfallRequests: boolean; // 避免瀑布式请求
prefetchOnHover: boolean; // 悬停时预加载
optimisticUpdates: boolean; // 乐观更新
staleWhileRevalidate: boolean; // SWR 策略
};
// 资源优化
resourceOptimization: {
imageOptimization: boolean; // 图片压缩和 WebP
lazyLoadImages: boolean; // 图片懒加载
bundleSizeAudit: boolean; // 包体积审计
treeShaking: boolean; // Tree Shaking
};
// 虚拟化
virtualization: {
listOver500Items: boolean; // 500+ 项列表虚拟化
correctItemSize: boolean; // itemSize 设置正确
overscanConfigured: boolean; // overscan 配置合理
};
}