React `memo`、`useMemo`、`useCallback` 配合使用及注意事项
React memo
、useMemo
、useCallback
配合使用及注意事项
这三个 API 是 React 性能优化的核心工具,主要用于避免不必要的渲染和减少重复计算。以下是它们的核心作用、配合方式及注意事项:
一、核心作用
API | 作用 |
---|---|
React.memo |
缓存组件:当父组件重新渲染时,若子组件的 props 未变化,阻止子组件重新渲染。 |
useMemo |
缓存计算结果:当依赖项未变化时,避免重复执行复杂计算(如过滤列表、复杂对象处理)。 |
useCallback |
缓存函数引用:当依赖项未变化时,返回同一个函数实例,避免子组件因函数引用变化而重新渲染。 |
二、配合使用场景
1. React.memo
+ useCallback
场景:父组件传递函数给子组件,避免子组件因函数引用变化而重新渲染。
// 子组件:用 React.memo 包裹
const Child = React.memo(({ onClick }) => {
console.log("子组件渲染");
return <button onClick={onClick}>点击</button>;
});
// 父组件:用 useCallback 缓存函数
const Parent = () => {
const [count, setCount] = useState(0);
// ✅ 使用 useCallback 避免每次渲染生成新函数
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []); // 无依赖
return (
<div>
<Child onClick={handleClick} />
<p>Count: {count}</p>
</div>
);
};
2. React.memo
+ useMemo
场景:父组件传递复杂对象或数组给子组件,避免因对象引用变化导致子组件重新渲染。
// 子组件:用 React.memo 包裹
const Child = React.memo(({ data }) => {
console.log("子组件渲染");
return <div>{data.value}</div>;
});
// 父组件:用 useMemo 缓存对象
const Parent = () => {
const [count, setCount] = useState(0);
// ✅ 使用 useMemo 缓存 data 对象
const data = useMemo(() => ({ value: "固定值" }), []); // 依赖为空,对象只创建一次
return (
<div>
<Child data={data} />
<button onClick={() => setCount(count + 1)}>更新父组件</button>
</div>
);
};
3. useMemo
+ useCallback
场景:当函数依赖某个计算结果时,同时使用两者优化。
const App = () => {
const [count, setCount] = useState(0);
const [input, setInput] = useState("");
// ✅ 缓存计算结果
const filteredList = useMemo(() => {
return largeList.filter(item => item.includes(input));
}, [input]); // 依赖 input 变化时重新计算
// ✅ 缓存函数
const handleAdd = useCallback(() => {
setCount(prev => prev + filteredList.length);
}, [filteredList]); // 依赖 filteredList 变化
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={handleAdd}>添加</button>
</div>
);
};
三、注意事项
1. 避免过度优化
- 优先考虑可读性:仅在性能瓶颈出现时使用这些 API。
- 简单组件无需使用:轻量级组件优化可能得不偿失(缓存本身有开销)。
2. 正确处理依赖数组
- 依赖缺失:可能导致缓存的值或函数引用过时。
- 依赖冗余:可能导致频繁重新计算。
// ❌ 错误:依赖缺失
const handleClick = useCallback(() => {
console.log(count); // count 始终为初始值
}, []); // 缺少 count 依赖
// ✅ 正确:添加依赖
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
3. React.memo
的局限性
- 浅比较问题:默认使用浅比较(
shallow compare
),若传递对象或数组,需结合useMemo
。 - 自定义比较函数:可通过第二个参数自定义 props 比较逻辑。
const Child = React.memo(({ data }) => {/* ... */}, (prevProps, nextProps) => {
return prevProps.data.id === nextProps.data.id; // 自定义比较
});
4. useMemo
的适用场景
- 复杂计算:如数据过滤、排序、数学计算等。
- 避免创建新对象:若依赖项未变,返回缓存的对象/数组。
// ❌ 错误:每次渲染生成新数组
const list = [{ id: 1 }, { id: 2 }];
// ✅ 正确:缓存数组
const list = useMemo(() => [{ id: 1 }, { id: 2 }], []);
5. useCallback
的替代方案
- 函数式更新:在
setState
中使用函数式更新,可减少依赖项。
const handleClick = useCallback(() => {
setCount(prev => prev + 1); // 无需依赖 count
}, []);
四、最佳实践
-
性能检测工具
使用 React DevTools 的 Profiler 分析组件渲染次数,确认优化是否有效。 -
优先使用
React.memo
对于频繁渲染的组件,优先尝试React.memo
,再结合useMemo
/useCallback
。 -
避免嵌套对象的浅比较问题
// ❌ 错误:data 每次渲染都是新对象,即使值相同 <Child data={{ id: 1 }} /> // ✅ 正确:用 useMemo 缓存对象 const data = useMemo(() => ({ id: 1 }), []); <Child data={data} />
-
函数传递优化
// ❌ 错误:每次渲染生成新函数,导致 Child 重新渲染 <Child onClick={() => { /* ... */ }} /> // ✅ 正确:用 useCallback 缓存函数 const handleClick = useCallback(() => { /* ... */ }, []); <Child onClick={handleClick} />
总结
React.memo
:用于组件层级,避免因父组件渲染导致子组件无意义渲染。useMemo
:用于缓存计算结果或复杂对象,减少重复计算和引用变化。useCallback
:用于缓存函数引用,避免子组件因函数变化重新渲染。
三者配合使用时,需注意依赖数组的正确性和浅比较的局限性,避免过度优化。最终目标是在保证可维护性的前提下,提升关键路径的性能。