By AriesZhou · · 3 min read
React Performance Optimization: Preventing Component Re-renders
React
In medium to large projects, a parent component’s state update often causes child components to re-render even when their props haven’t changed. These unnecessary renders accumulate and significantly affect performance. Mastering techniques to prevent re-renders is key to React performance optimization.
React’s re-rendering mechanism is central to performance. Understanding when and how to prevent unnecessary renders can effectively improve page responsiveness.
1. Re-render Triggers
React components re-render in the following cases:
statechangespropschanges- Parent component re-renders
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 Basic Usage
React.memo performs a shallow comparison for function components:
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 Custom Comparison Function
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 Memoizing Computed Values
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 Avoiding Object Reference Changes
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 Memoizing Callback Functions
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 Using with 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. Class Component Optimization
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. Common Pitfalls
6.1 Inline Function Issues
function Component() {
return (
<div>
{/* ❌ 每次渲染创建新函数 */}
<Child onClick={() => handleClick(id)} />
{/* ✅ 使用 useCallback */}
<Child onClick={useCallback(() => handleClick(id), [id])} />
</div>
);
}
6.2 Array and Object Literals
function Component({ items }) {
return (
<div>
{/* ❌ 新数组引用 */}
<Child items={[...items, newItem]} />
{/* ✅ 使用 useMemo */}
<Child items={useMemo(() => [...items, newItem], [items, newItem])} />
</div>
);
}
7. Performance Optimization Checklist
| Optimization method | Use case | Priority |
|---|---|---|
| React.memo | Function components | High |
| useMemo | Cache computed values | High |
| useCallback | Cache callback functions | High |
| shouldComponentUpdate | Class components | Medium |
| PureComponent | Class components | Medium |
8. Summary
Preventing unnecessary re-renders:
- Prefer React.memo: the primary optimization for function components
- Use useMemo/useCallback judiciously: don’t overuse them, or you’ll increase memory overhead
- Avoid inline functions and objects: define them outside the component or cache them with Hooks
Tip: before optimizing, use React DevTools Profiler to confirm the performance bottleneck and avoid premature optimization.