· 3 min read
React 性能优化:防止组件重新渲染
React
在中大型项目中,经常看到父组件状态更新,子组件即使 props 没变也会重新渲染,这种不必要的渲染累积起来会显著影响性能。掌握防止重新渲染的技巧,是 React 性能优化的关键。
React 的重新渲染机制是性能的核心。理解何时以及如何防止不必要的渲染,能有效改善页面响应速度。
1. 重新渲染的触发条件
React 组件在以下情况会重新渲染:
state变化props变化- 父组件重新渲染
graph TD
A[组件渲染] --> B{状态变化?}
B -->|是| C[重新渲染]
B -->|否| D{Props 变化?}
D -->|是| C
D -->|否| E{父组件渲染?}
E -->|是| F{浅比较不同?}
E -->|否| G[不重新渲染]
F -->|是| C
F -->|否| G
2. React.memo
2.1 基本用法
React.memo 对函数组件进行浅比较:
function ChildComponent({ name, onClick }) {
console.log('Child 渲染了');
return <button onClick={onClick}>{name}</button>;
}
// 只有 props 变化时才重新渲染
const MemoizedChild = React.memo(ChildComponent);
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<p>父组件计数: {count}</p>
<button onClick={() => setCount(c => c + 1)}>增加</button>
{/* count 变化时,MemoizedChild 不会重新渲染 */}
<MemoizedChild name="子组件" onClick={() => {}} />
</div>
);
}
2.2 自定义比较函数
function MyComponent({ data }) {
return <div>{data.value}</div>;
}
// 自定义比较逻辑
const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => {
return prevProps.data.id === nextProps.data.id;
});
3. useMemo
3.1 缓存计算结果
function ExpensiveComponent({ list, filter }) {
// 只有 list 或 filter 变化时才重新计算
const filteredList = useMemo(() => {
console.log('计算中...');
return list.filter(item => item.name.includes(filter));
}, [list, filter]);
return (
<ul>
{filteredList.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
3.2 避免对象引用变化
function Component({ a, b }) {
// ❌ 每次渲染都创建新对象
const config = { mode: 'dark', size: 'large' };
// ✅ 使用 useMemo 缓存对象
const config = useMemo(() => ({
mode: 'dark',
size: 'large',
}), []);
// ✅ 数组同理
const items = useMemo(() => [a, b], [a, b]);
}
4. useCallback
4.1 缓存回调函数
function Parent() {
const [count, setCount] = useState(0);
// ❌ 每次渲染都创建新函数
const handleClick = () => console.log('click');
// ✅ 缓存回调函数
const handleClick = useCallback(() => {
console.log('click');
}, []);
// ✅ 带依赖的回调
const handleIncrement = useCallback(() => {
setCount(c => c + 1);
}, []);
return <Child onClick={handleClick} onIncrement={handleIncrement} />;
}
4.2 配合 React.memo 使用
function List({ items, onItemClick }) {
console.log('List 渲染');
return (
<ul>
{items.map(item => (
<ListItem
key={item.id}
item={item}
onClick={onItemClick}
/>
))}
</ul>
);
}
const MemoizedList = React.memo(List);
function App() {
const [query, setQuery] = useState('');
const [items] = useState([{ id: 1, name: '苹果' }, { id: 2, name: '香蕉' }]);
const handleClick = useCallback((id) => {
console.log('点击了', id);
}, []);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<MemoizedList items={items} onItemClick={handleClick} />
</div>
);
}
5. 类组件优化
5.1 shouldComponentUpdate
class Counter extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 只在 count 变化时更新
return nextProps.count !== this.props.count;
}
render() {
return <div>计数: {this.props.count}</div>;
}
}
5.2 PureComponent
class UserList extends React.PureComponent {
render() {
// 自动进行浅比较
return (
<ul>
{this.props.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
}
6. 常见陷阱
6.1 内联函数问题
function Component() {
return (
<div>
{/* ❌ 每次渲染创建新函数 */}
<Child onClick={() => handleClick(id)} />
{/* ✅ 使用 useCallback */}
<Child onClick={useCallback(() => handleClick(id), [id])} />
</div>
);
}
6.2 数组和对象字面量
function Component({ items }) {
return (
<div>
{/* ❌ 新数组引用 */}
<Child items={[...items, newItem]} />
{/* ✅ 使用 useMemo */}
<Child items={useMemo(() => [...items, newItem], [items, newItem])} />
</div>
);
}
7. 性能优化 Checklist
| 优化方法 | 适用场景 | 优先级 |
|---|---|---|
| React.memo | 函数组件 | 高 |
| useMemo | 缓存计算结果 | 高 |
| useCallback | 缓存回调函数 | 高 |
| shouldComponentUpdate | 类组件 | 中 |
| PureComponent | 类组件 | 中 |
8. 总结
防止不必要的重新渲染:
- 优先使用 React.memo: 函数组件的首选优化方式
- 合理使用 useMemo/useCallback: 不要过度使用,否则会增加内存开销
- 避免内联函数和对象: 组件外部定义或使用 Hook 缓存
提示: 优化前先用 React DevTools 的 Profiler 确认性能瓶颈,避免过早优化。