前言
当 React 应用变得庞大时,性能问题逐渐浮现:不必要的重渲染、缓慢的初始加载、庞大的静态资源。本文将系统性地讲解 React 性能优化的各种策略,帮助你打造流畅的用户体验。

React 渲染机制
虚拟 DOM 的工作原理
React 使用虚拟 DOM 来减少对真实 DOM 的直接操作。每次状态变化时,React 会生成新的虚拟 DOM 树,然后与旧的虚拟 DOM 树进行 Diff 比较,最后只更新真实 DOM 中变化的部分。
1 2 3 4 5 6 7
| interface VNode { type: string | Function; props: Record<string, any>; children: VNode[]; key: string | number | null; }
|
什么触发了重渲染
React 组件在以下情况下会重新渲染:
- 状态更新:
useState 的 setter 被调用
- 父组件重渲染:子组件默认会跟随父组件一起渲染
- Context 变化:消费了某个 Context 的组件
- Hooks 依赖变化:使用了外部状态
关键认知:React 的重渲染并不一定意味着低性能。但如果重渲染频率过高且组件本身昂贵,就可能产生性能问题。
优化策略
React.memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { memo, useState } from 'react';
function ExpensiveChild({ data }: { data: string[] }) { console.log('Rendering ExpensiveChild'); return ( <ul> {data.map(item => <li key={item}>{item}</li>)} </ul> ); }
const MemoizedChild = memo(ExpensiveChild);
function Parent() { const [count, setCount] = useState(0); const data = ['Apple', 'Banana', 'Cherry'];
return ( <div> <button onClick={() => setCount(c => c + 1)}> Count: {count} </button> <MemoizedChild data={data} /> </div> ); }
|
自定义比较函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const MemoizedList = memo( function UserList({ users }: { users: User[] }) { return ( <div> {users.map(user => ( <UserCard key={user.id} user={user} /> ))} </div> ); }, (prevProps, nextProps) => { return ( prevProps.users.length === nextProps.users.length && prevProps.users[0]?.id === nextProps.users[0]?.id ); } );
|
useMemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { useMemo, useState } from 'react';
function Dashboard({ transactions }: { transactions: Transaction[] }) { const [filter, setFilter] = useState('all');
const stats = useMemo(() => { console.log('Computing expensive stats...'); return { total: transactions.reduce((sum, t) => sum + t.amount, 0), count: transactions.length, average: transactions.reduce((sum, t) => sum + t.amount, 0) / transactions.length, maxAmount: Math.max(...transactions.map(t => t.amount)), }; }, [transactions, filter]);
return ( <div> <h2>Dashboard</h2> <p>Total: {stats.total}</p> <p>Count: {stats.count}</p> <p>Average: {stats.average.toFixed(2)}</p> <p>Max: {stats.maxAmount}</p> </div> ); }
|
useCallback
useCallback 返回一个记忆化的回调函数,依赖项不变时返回的是同一个函数引用。这对于传递给子组件的回调函数尤为重要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import { useCallback, useState, memo } from 'react';
const ChildButton = memo(function ChildButton({ onClick, label }: { onClick: () => void; label: string; }) { console.log(`Rendering ${label}`); return <button onClick={onClick}>{label}</button>; });
function Parent() { const [count, setCount] = useState(0); const [other, setOther] = useState(0);
const increment = useCallback(() => { setCount(c => c + 1); }, []);
return ( <div> <ChildButton onClick={increment} label="Increment" /> <button onClick={() => setOther(o => o + 1)}> Other: {other} </button> </div> ); }
|
虚拟化长列表
react-window
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }: { items: string[] }) { const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => ( <div style={style} className="list-item"> <span>{index + 1}</span> <span>{items[index]}</span> </div> );
return ( <List height={600} itemCount={items.length} itemSize={50} width="100%" > {Row} </List> ); }
|
性能对比
| 列表长度 |
普通渲染 |
虚拟化渲染 |
| 100 条 |
~5ms |
~3ms |
| 1,000 条 |
~30ms |
~4ms |
| 10,000 条 |
~300ms |
~5ms |
| 100,000 条 |
页面卡死 |
~6ms |
代码分割与懒加载
React.lazy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./components/Dashboard')); const Settings = lazy(() => import('./components/Settings')); const Analytics = lazy(() => import('./components/Analytics'));
function App() { return ( <Suspense fallback={<div className="loading-skeleton">Loading...</div>}> <Router> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> <Route path="/analytics" element={<Analytics />} /> </Router> </Suspense> ); }
|
预加载策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function NavLink({ to, children }: { to: string; children: React.ReactNode }) { const handleMouseEnter = () => { if (to === '/analytics') { import('./components/Analytics'); } else if (to === '/settings') { import('./components/Settings'); } };
return ( <Link to={to} onMouseEnter={handleMouseEnter}> {children} </Link> ); }
|
状态管理优化
细粒度状态
1 2 3 4 5 6 7 8 9 10 11 12
| const [state, setState] = useState({ name: '', age: 0, address: '', preferences: {} });
const [name, setName] = useState(''); const [age, setAge] = useState(0); const [address, setAddress] = useState('');
|
Context 拆分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const ThemeContext = createContext<Theme>('light'); const UserContext = createContext<User | null>(null); const SettingsContext = createContext<Settings>(defaultSettings);
function Header() { const theme = useContext(ThemeContext); return <header className={theme}>...</header>; }
function Profile() { const user = useContext(UserContext); return <div>{user?.name}</div>; }
|
图片优化
懒加载图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function LazyImage({ src, alt }: { src: string; alt: string }) { const [loaded, setLoaded] = useState(false); const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setLoaded(true); observer.disconnect(); } }, { rootMargin: '200px' } );
if (imgRef.current) { observer.observe(imgRef.current); }
return () => observer.disconnect(); }, []);
return ( <img ref={imgRef} src={loaded ? src : undefined} alt={alt} className="lazy-image" /> ); }
|
性能度量工具
React DevTools 内置的 Profiler 面板可以记录每次渲染的耗时,帮助定位性能瓶颈。
useWhyDidYouUpdate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function useWhyDidYouUpdate(name: string, props: Record<string, any>) { const previousProps = useRef<Record<string, any>>();
useEffect(() => { if (previousProps.current) { const changedProps: Record<string, [any, any]> = {}; const allKeys = Object.keys({ ...previousProps.current, ...props });
for (const key of allKeys) { if (previousProps.current[key] !== props[key]) { changedProps[key] = [previousProps.current[key], props[key]]; console.log(`[${name}] "${key}" changed:`, changedProps[key]); } } } previousProps.current = props; }); }
|
总结
React 性能优化是一个系统工程,按优先级排列:
- 代码分割:首屏只加载必需代码,最大提升 TTI
- 虚拟化长列表:DOM 节点数量是性能杀手
- React.memo + useMemo + useCallback:减少不必要的重渲染
- 状态拆分与 Context 分离:缩小重渲染影响范围
- 图片懒加载与优化:减少网络请求和内存占用
记住:「过早优化是万恶之源」。先用工具找到真正的瓶颈,再有针对性地优化。