简单回顾下useMemo

一、useMemo 的作用和意义

useMemo 是 React 提供的一个 Hook,用于‌性能优化 ‌,它的主要作用是‌缓存计算结果‌,避免在每次渲染时都进行不必要的复杂计算。

核心意义:

  1. 性能优化‌:避免重复执行昂贵的计算
  2. 引用稳定性‌:保持对象/数组的引用不变,避免子组件不必要的重新渲染
  3. 按需计算‌:只在依赖项变化时才重新计算值

二、基本语法

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>;
}

四、使用注意事项

  1. 不要滥用‌:不是所有计算都需要useMemo,简单计算可能比useMemo开销更大
  2. 依赖项要准确‌:确保所有在计算函数中使用的外部值都包含在依赖项中
  3. 避免副作用‌:计算函数应该是纯函数,不应包含副作用
  4. 与React.memo配合‌:useMemo缓存值,React.memo缓存组件
  5. 不是防抖/节流‌: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中的复杂值,确保引用稳定

  • 两者配合可以最大程度减少不必要的渲染:

    ini 复制代码
    const 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

相关推荐
Dontla9 分钟前
Webpack DefinePlugin插件介绍(允许在编译时创建JS全局常量,常量可以在源代码中直接使用)JS环境变量
运维·javascript·webpack
酷爱码1 小时前
css中的 vertical-align与line-height作用详解
前端·css
沐土Arvin1 小时前
深入理解 requestIdleCallback:浏览器空闲时段的性能优化利器
开发语言·前端·javascript·设计模式·html
专注VB编程开发20年1 小时前
VB.NET关于接口实现与简化设计的分析,封装其他类
java·前端·数据库
小妖6661 小时前
css 中 content: “\e6d0“ 怎么变成图标的?
前端·css
L耀早睡2 小时前
mapreduce打包运行
大数据·前端·spark·mapreduce
咖啡の猫2 小时前
JavaScript基础-创建对象的三种方式
开发语言·javascript·ecmascript
HouGISer2 小时前
副业小程序YUERGS,从开发到变现
前端·小程序
outstanding木槿2 小时前
react中安装依赖时的问题 【集合】
前端·javascript·react.js·node.js
小吕学编程3 小时前
Jackson使用详解
java·javascript·数据库·json