说说 React 性能优化的手段有哪些?
一、是什么
React 性能优化是指通过各种技术手段减少不必要的计算和 DOM 操作,使应用保持流畅的用户体验。React 的虚拟 DOM 和 diff 算法已经提供了基本的性能保障,但在复杂应用中,仍然需要开发者有意识地进行优化。
性能优化的核心方向:
减少渲染次数 → 让不该渲染的组件跳过 render
减少渲染量级 → 让必须渲染的组件更快完成
减少加载体积 → 让用户更快看到首屏内容
二、组件记忆化
React.memo
用于函数组件,浅比较 props,props 不变时跳过重新渲染:
interface UserCardProps {
name: string;
avatar: string;
role: string;
}
const UserCard = React.memo(function UserCard({ name, avatar, role }: UserCardProps) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<span>{role}</span>
</div>
);
});
useMemo — 缓存计算结果
避免每次渲染都执行昂贵的计算:
function AnalyticsTable({ data, sortKey, filter }: Props) {
const processedData = useMemo(() => {
const filtered = data.filter(item =>
item.category === filter || filter === 'all'
);
return filtered.sort((a, b) => {
if (a[sortKey] < b[sortKey]) return -1;
if (a[sortKey] > b[sortKey]) return 1;
return 0;
});
}, [data, sortKey, filter]);
return (
<table>
<tbody>
{processedData.map(row => (
<tr key={row.id}>
<td>{row.name}</td>
<td>{row.value}</td>
</tr>
))}
</tbody>
</table>
);
}
useCallback — 缓存函数引用
配合 React.memo 防止子组件因函数引用变化而重新渲染:
function TodoApp() {
const [todos, setTodos] = useState<Todo[]>([]);
const addTodo = useCallback((text: string) => {
setTodos(prev => [...prev, { id: crypto.randomUUID(), text, done: false }]);
}, []);
const toggleTodo = useCallback((id: string) => {
setTodos(prev =>
prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
);
}, []);
return (
<div>
<TodoInput onAdd={addTodo} />
<MemoizedTodoList todos={todos} onToggle={toggleTodo} />
</div>
);
}
三、代码分割与懒加载
React.lazy + Suspense
按路由或组件级别进行代码分割,减少初始 bundle 体积:
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Reports = lazy(() => import('./pages/Reports'));
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Suspense>
);
}
四、列表虚拟化
对于长列表(数百或数千项),只渲染可视区域内的元素,大幅减少 DOM 节点数量:
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: () => 60,
overscan: 5,
});
return (
<div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
style={{
position: 'absolute',
top: 0,
transform: `translateY(${virtualRow.start}px)`,
height: `${virtualRow.size}px`,
width: '100%',
}}
>
<ItemRow item={items[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
虚拟化可以让 10000 条数据的列表和 20 条数据的列表保持相同的渲染性能。
五、状态管理优化
状态下放
将频繁变化的状态放在尽可能低的层级,避免不相关的组件重新渲染:
// 优化前:整个 Page 因为 hover 状态重新渲染
function Page() {
const [hoveredId, setHoveredId] = useState<string | null>(null);
return (
<div>
<Sidebar />
<ItemList hoveredId={hoveredId} onHover={setHoveredId} />
</div>
);
}
// 优化后:hover 状态下放到 ItemList 内部
function Page() {
return (
<div>
<Sidebar />
<ItemList />
</div>
);
}
Context 拆分
避免将不同更新频率的数据放在同一个 Context 中,拆分为独立的 ThemeContext、AuthContext 等,使消费者只订阅自己关心的数据。
六、并发特性
useTransition
将低优先级更新标记为 transition,让高优先级的用户交互保持流畅:
function TabContainer() {
const [tab, setTab] = useState('home');
const [isPending, startTransition] = useTransition();
const switchTab = (nextTab: string) => {
startTransition(() => {
setTab(nextTab);
});
};
return (
<div>
<nav>
{['home', 'posts', 'contacts'].map(t => (
<button key={t} onClick={() => switchTab(t)} className={tab === t ? 'active' : ''}>
{t}
</button>
))}
</nav>
<div style={{ opacity: isPending ? 0.7 : 1 }}>
<TabContent tab={tab} />
</div>
</div>
);
}
useDeferredValue
延迟更新低优先级的值,适用于依赖外部传入值的场景:
function SearchResults({ query }: { query: string }) {
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
const results = useMemo(
() => heavySearch(deferredQuery),
[deferredQuery]
);
return (
<div style={{ opacity: isStale ? 0.6 : 1 }}>
{results.map(item => (
<ResultItem key={item.id} item={item} />
))}
</div>
);
}
七、避免常见的性能陷阱
避免在 render 中创建新对象和函数
// 不推荐:每次渲染创建新的 style 对象和内联函数
<MemoizedCard
style={{ padding: '16px' }}
onClick={() => handleClick(item.id)}
/>
// 推荐:提取为模块级常量或使用 hooks 缓存
const cardStyle = { padding: '16px' };
function Parent() {
const handleClick = useCallback((id: string) => { /* ... */ }, []);
return <MemoizedCard style={cardStyle} onItemClick={handleClick} />;
}
正确使用 key
使用稳定唯一的业务 ID 而非 index,避免列表变动时组件状态错乱和不必要的 DOM 操作。
防抖与节流
对搜索输入、滚动、窗口 resize 等高频事件使用 debounce/throttle,减少触发渲染和网络请求的频率。
八、性能分析工具
使用 React DevTools Profiler 录制渲染过程,定位渲染耗时超过 16ms 的组件。配合 <Profiler> 组件可以在代码中收集渲染指标:
import { Profiler } from 'react';
function App() {
return (
<Profiler id="App" onRender={(id, phase, duration) => {
if (duration > 16) console.warn(`${id} 渲染耗时 ${duration.toFixed(2)}ms`);
}}>
<MainContent />
</Profiler>
);
}
十、React Compiler
React 19 引入的 React Compiler 能在编译时自动完成大部分记忆化优化:
// 开发者只需要编写简洁的代码
function ProductList({ products, category }) {
const filtered = products.filter(p => p.category === category);
return (
<ul>
{filtered.map(product => (
<ProductCard key={product.id} product={product} />
))}
</ul>
);
}
// React Compiler 在编译时自动分析并插入等效的 useMemo/memo 逻辑
React Compiler 遵循 React 的规则(纯函数组件、Hooks 规则等),只要代码符合这些约定,就能获得自动优化。
十一、总结
性能优化的首要原则是度量先行:使用 Profiler 定位真正的瓶颈,针对性地优化,避免为了优化而优化。过早或过度的优化会增加代码复杂性,反而不利于维护。