一、useMemo 的作用和意义
useMemo
是 React 提供的一个 Hook,用于性能优化 ,它的主要作用是缓存计算结果,避免在每次渲染时都进行不必要的复杂计算。
核心意义:
- 性能优化:避免重复执行昂贵的计算
- 引用稳定性:保持对象/数组的引用不变,避免子组件不必要的重新渲染
- 按需计算:只在依赖项变化时才重新计算值
二、基本语法
scss
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 第一个参数:计算函数,返回要缓存的值
- 第二个参数:依赖项数组,只有当依赖项变化时才会重新计算
三、实际应用场景
1. 复杂计算缓存
javascript
function ExpensiveComponent({ list }) {
const sortedList = useMemo(() => {
console.log('重新排序...');
return [...list].sort((a, b) => a.value - b.value);
}, [list]);
return <div>{sortedList.map(item => <div key={item.id}>{item.value}</div>)}</div>;
}
2. 避免子组件不必要的渲染
javascript
jsxCopy Code
function Parent({ a, b }) {
const childProps = useMemo(() => ({ a, b }), [a, b]);
return <Child {...childProps} />;
}
// 只有当a或b变化时,Child才会重新渲染
const Child = React.memo(({ a, b }) => {
return <div>{a} - {b}</div>;
});
3. 大型列表或数据转换
javascript
jsxCopy Code
function DataTable({ rawData }) {
const processedData = useMemo(() => {
return rawData.map(item => ({
...item,
fullName: `${item.firstName} ${item.lastName}`,
formattedDate: formatDate(item.timestamp)
}));
}, [rawData]);
return <Table data={processedData} />;
}
4. 避免昂贵的初始化
typescript
jsxCopy Code
function ComponentWithHeavyInit({ type }) {
const config = useMemo(() => {
return getExpensiveConfig(type);
}, [type]);
return <div>{config.title}</div>;
}
四、使用注意事项
- 不要滥用:不是所有计算都需要useMemo,简单计算可能比useMemo开销更大
- 依赖项要准确:确保所有在计算函数中使用的外部值都包含在依赖项中
- 避免副作用:计算函数应该是纯函数,不应包含副作用
- 与React.memo配合:useMemo缓存值,React.memo缓存组件
- 不是防抖/节流:useMemo不是用来控制函数执行频率的
五、高级面试题及答案
1. useMemo和useCallback有什么区别?
useMemo
用于缓存计算结果,返回的是计算得到的值useCallback
用于缓存函数本身,返回的是函数引用- 本质上,
useCallback(fn, deps)
等同于useMemo(() => fn, deps)
2. 什么情况下不应该使用useMemo?
- 计算非常简单,开销小于useMemo本身
- 依赖项频繁变化,导致频繁重新计算
- 组件很少重新渲染,优化效果不明显
- 计算函数有副作用(应该使用useEffect)
3. useMemo能保证引用不变吗?
- 在依赖项不变的情况下,useMemo会返回相同的引用
- 但React可能会在内存压力大时释放缓存(理论上,实践中很少见)
- 对于必须保持引用稳定的场景,可以考虑使用ref
4. 如何测试useMemo的效果?
- 使用React DevTools的Profiler测量渲染性能
- 添加console.log到计算函数中观察调用频率
- 比较使用前后的组件渲染次数和耗时
- 注意不要仅凭感觉判断优化效果
5. useMemo和React.memo如何配合使用?
-
React.memo
缓存组件,避免props未变时的重新渲染 -
useMemo
缓存props中的复杂值,确保引用稳定 -
两者配合可以最大程度减少不必要的渲染:
iniconst MemoizedChild = React.memo(Child); function Parent({ items }) { const processedItems = useMemo(() => processItems(items), [items]); return <MemoizedChild items={processedItems} />; }
1. useMemo 的同步特性
useMemo
是同步执行的 Hook,设计初衷是用于同步计算和缓存值- 直接在
useMemo
中返回 Promise 会导致缓存的是 Promise 对象而非实际结果 - 异步操作在
useMemo
中不会被立即执行,而是会被放入事件队列延迟处理
2. 常见错误用法示例
scss
// 错误用法1:直接返回Promise
const data = useMemo(() => fetchData(), []); // 缓存的是Promise对象
// 错误用法2:使用async/await语法
const data = useMemo(async () => {
return await fetchData(); // 同样返回Promise
3. 替代方案
对于需要缓存异步数据的场景,推荐以下解决方案:
方案A:useState + useEffect 组合
scss
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, [deps]);
方案B:自定义 useAsyncMemo Hook
scss
function useAsyncMemo(factory, deps) {
const [value, setValue] = useState(null);
useEffect(() => {
factory().then(setValue);
}, deps);
return value;
}
// 使用示例
const data = useAsyncMemo(() => fetchData(), [deps]);
方案C:使用第三方库
如 use-async-memo
库专门处理异步记忆化场景1