3、React.memo:函数组件的记忆化、浅比较与深比较、使用场景与注意事项
好,咱们来聊聊 React.memo。这个东西,说白了就是给函数组件加一层「缓存」。
我记得刚接触 React 时,最头疼的就是「明明数据没变,组件为啥又重渲染了?」。后来我才明白,React 默认情况下,只要父组件重新渲染,子组件就会跟着跑一遍。哪怕你传给子组件的 props 完全没变。
React.memo 就是来解决这个问题的。它像一个守门员,检查 props 有没有变化。没变?那就不重新渲染,直接用上次的结果。
React.memo 的基本用法
用法其实很简单。你写一个函数组件,然后用 React.memo 包一下:
const MyComponent = React.memo(function MyComponent(props) {
// 你的组件逻辑
return <div>{props.name}</div>;
});
或者用箭头函数:
const MyComponent = React.memo(({ name }) => {
return <div>{name}</div>;
});
嗯,就这么简单。但真正用起来,坑可不少。
浅比较:React.memo 的默认行为
React.memo 默认用的是「浅比较」。什么意思?就是只比较 props 对象的第一层属性。
举个例子:
const Child = React.memo(({ user }) => {
console.log('Child 渲染了');
return <div>{user.name}</div>;
});
function Parent() {
const [count, setCount] = useState(0);
// 每次渲染都创建新对象
const user = { name: '张三', age: 25 };
return (
<div>
<button onClick={() => setCount(c => c + 1)}>点击:{count}</button>
<Child user={user} />
</div>
);
}
你猜怎么着?每次点按钮,Child 都会重新渲染。为什么?因为每次渲染时,user 对象都是新创建的。浅比较发现两个对象的引用不同,就认为 props 变了。
深比较:什么时候需要?
说实话,我在项目中很少用深比较。因为深比较本身有性能开销,你想想看,每次渲染都要递归遍历整个对象,这反而可能拖慢速度。
但有些场景确实需要。比如你从后端拿到的数据,每次都是新对象,但内容一样:
const areEqual = (prevProps, nextProps) => {
// 手动实现深比较
return JSON.stringify(prevProps) === JSON.stringify(nextProps);
};
const Child = React.memo(({ data }) => {
return <div>{data.length} 条数据</div>;
}, areEqual);
我个人习惯用 lodash.isEqual 来做深比较,但只在确实需要的时候才用。别滥用。
使用场景:什么时候该用 React.memo?
不是所有组件都适合用 React.memo。我总结了几种典型场景:
- 纯展示组件:只根据 props 渲染,没有内部状态
- 频繁重渲染的组件:比如列表中的每一项
- 计算量大的组件:渲染需要大量计算或数据处理的
- 被多个父组件复用的组件:避免不必要的重复渲染
举个例子,一个商品列表:
const ProductItem = React.memo(({ product, onAddToCart }) => {
console.log(`渲染商品:${product.name}`);
return (
<div>
<h4>{product.name}</h4>
<p>¥{product.price}</p>
<button onClick={() => onAddToCart(product.id)}>加入购物车</button>
</div>
);
});
当列表有 100 个商品时,如果不用 React.memo,每次父组件更新都会重新渲染所有商品。用了之后,只有数据变化的商品才会重新渲染。
注意事项:避坑指南
我曾经在一个项目里,给所有组件都套上了 React.memo。结果性能反而更差了。为什么?因为 React.memo 本身也有开销——每次渲染都要做一次比较。
还有几个坑要注意:
- 函数作为 props:每次渲染都会创建新函数,导致 React.memo 失效。需要用 useCallback 包裹
- 对象/数组作为 props:同上,需要用 useMemo 或保持引用稳定
- children 作为 props:如果父组件传了 JSX 作为 children,那每次都是新对象
- 不要滥用:简单的组件,渲染开销很小,加 React.memo 反而得不偿失
来看一个典型的「函数 props 问题」:
function Parent() {
const handleClick = () => {
console.log('点击了');
};
return <Child onClick={handleClick} />;
}
// 这个 memo 基本没用,因为 handleClick 每次都是新函数
const Child = React.memo(({ onClick }) => {
return <button onClick={onClick}>点击</button>;
});
正确的做法是用 useCallback:
function Parent() {
const handleClick = useCallback(() => {
console.log('点击了');
}, []); // 依赖数组为空,函数引用不变
return <Child onClick={handleClick} />;
}
性能测试:到底值不值得用?
我建议你在用之前先做个小测试。打开 React DevTools 的 Profiler,看看组件的渲染次数和耗时。
| 场景 | 不用 React.memo | 用 React.memo | 建议 |
|---|---|---|---|
| 简单文本渲染 | 0.1ms | 0.15ms(含比较) | 不用 |
| 复杂列表(100项) | 50ms | 5ms | 用 |
| 图表组件 | 200ms | 10ms | 必须用 |
你看,对于简单组件,加 React.memo 反而更慢。但对于复杂组件,效果立竿见影。
总结一下
React.memo 是个好东西,但要用对地方。我的建议是:
- 先分析性能瓶颈,别盲目加
- 配合 useCallback 和 useMemo 一起用
- 注意浅比较的局限性
- 深比较慎用,除非你真的需要
嗯,记住一句话:React.memo 是优化工具,不是银弹。用对了事半功倍,用错了反而添乱。