React 性能优化:memo、useMemo、useCallback 实战
掌握 React 三大性能优化 API,让你的应用告别不必要的重渲染。 · 难度:进阶 · +20XP
你的 React 应用为什么慢?
React 的默认行为是:父组件更新,所有子组件都跟着重新渲染。听起来很浪费?是的,但在大多数情况下,这种"浪费"其实影响不大——现代浏览器渲染 100 个简单组件可能只需要几毫秒。
但当你的应用有几百个列表项、复杂图表、实时数据流时,不必要的重渲染就会让页面明显卡顿。这时候你需要 React 的性能优化三件套。
React.memo:阻止不必要的子组件渲染
React.memo 是一个高阶组件,它告诉 React:"如果 props 没变,就别重新渲染这个组件"。
// 普通组件:父组件更新 → 子组件一定重新渲染
function TodoItem({ text, done }) {
console.log('TodoItem 渲染了:', text);
return <li style={{ color: done ? 'green' : 'black' }}>{text}</li>;
}
// 用 memo 包裹:props 没变 → 跳过渲染
const TodoItem = React.memo(function TodoItem({ text, done }) {
console.log('TodoItem 渲染了:', text);
return <li style={{ color: done ? 'green' : 'black' }}>{text}</li>;
});
当你勾选一个待办事项时,只有那个被勾选的 TodoItem 重新渲染,其他的全部跳过。
useMemo:缓存计算结果
假设你有一个计算量很大的操作——比如对 10000 条数据进行排序和过滤:
function ProductList({ products, category }) {
// ❌ 每次渲染都重新计算,即使 products 和 category 没变
const filtered = products
.filter(p => p.category === category)
.sort((a, b) => b.sales - a.sales);
// ✅ 只在依赖变化时重新计算
const filtered = useMemo(() => {
return products
.filter(p => p.category === category)
.sort((a, b) => b.sales - a.sales);
}, [products, category]);
return filtered.map(p => <ProductCard key={p.id} product={p} />);
}
| Hook | 缓存什么 | 何时用 |
|---|---|---|
React.memo | 整个组件 | 子组件 props 不常变、渲染成本高 |
useMemo | 计算结果(值) | 计算量大、依赖稳定的操作 |
useCallback | 函数引用 | 传给 memo 子组件的回调函数 |
useCallback:稳定的函数引用
这是最容易用错的一个。看这个场景:
function Parent() {
const [count, setCount] = useState(0);
// ❌ 每次渲染都创建新的函数引用
const handleClick = () => setCount(c => c + 1);
// ✅ 函数引用保持稳定
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // 依赖为空,函数永远不会变
return <Child onClick={handleClick} />;
}
const Child = React.memo(function Child({ onClick }) {
console.log('Child 渲染');
return <button onClick={onClick}>+1</button>;
});
不用 useCallback 的话:每次 count 变化 → Parent 重渲染 → handleClick 是新函数 → memo 的 Child 发现 props 变了 → 也重渲染。memo 白用了。
用了 useCallback:handleClick 引用不变 → Child 的 props 没变 → memo 生效 → 跳过渲染。
什么时候不应该用这些优化?
过早优化是万恶之源。以下情况不需要加 memo/useMemo/useCallback:
- 组件本身渲染很快(一个简单的 div 或 span)
- props 一直在变(加了 memo 也是每次都要比较,白费开销)
- 应用规模小(少于 100 个组件)
- 还没测量过性能(先用 React DevTools Profiler 找到真正的瓶颈)
实战:优化一个待办列表
// 场景:100 个待办事项,可以勾选完成、可以添加新项
function TodoApp() {
const [todos, setTodos] = useState([...100个待办...]);
const [newText, setNewText] = useState('');
// ✅ addTodo 引用稳定,不会导致 TodoList 重渲染
const addTodo = useCallback((text) => {
setTodos(prev => [...prev, { id: Date.now(), text, done: false }]);
}, []);
// ✅ toggleTodo 引用稳定
const toggleTodo = useCallback((id) => {
setTodos(prev => prev.map(t =>
t.id === id ? { ...t, done: !t.done } : t
));
}, []);
return (
<div>
<AddTodo onAdd={addTodo} />
<TodoList todos={todos} onToggle={toggleTodo} />
</div>
);
}
// ✅ 用 memo 包裹,只在 todos 变化时渲染
const TodoList = React.memo(function TodoList({ todos, onToggle }) {
return todos.map(todo =>
<TodoItem key={todo.id} todo={todo} onToggle={onToggle} />
);
});
动手试试
- 基础练习:创建一个带输入框和列表的组件,用 React DevTools Profiler 查看每次输入时的渲染次数,然后加上 memo/useCallback 对比
- 进阶应用:做一个 1000 行的虚拟列表,用 useMemo 缓存可见行,用 React.memo 优化每一行
- 项目实战:找到你项目中最"重"的页面(渲染次数最多),加 memo/useMemo/useCallback 优化,用 Profiler 量化优化效果
接下来学什么?
掌握了性能优化,下一课学习 React 自定义 Hook 实战——把重复的逻辑抽成 Hook,让代码更干净、更可复用。
📖 译自 freeCodeCamp React Performance Optimization (CC BY 4.0),有改编和扩充。