useMemo的10大使用场景

useMemo 是 React Hooks 中用于性能优化的核心 API,它的核心作用是缓存计算结果,避免在每次渲染时重复执行昂贵的计算 。以下是 useMemo 的详细应用场景、代码示例及最佳实践:


一、useMemo 的核心机制

  • 输入:一个函数(计算逻辑)和一个依赖项数组。
  • 输出:当依赖项变化时,重新执行函数并返回新值;否则返回缓存值。
  • 适用条件:当计算成本较高或需要稳定引用时使用。
jsx 复制代码
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

二、useMemo 的 10 个典型应用场景

1. 复杂计算缓存

场景 :组件内部有耗时计算(如大数据过滤、排序、数学运算)。
示例:计算斐波那契数列

jsx 复制代码
const fibonacci = (n) => {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
};

const Component = ({ n }) => {
  // 未优化:每次渲染都重新计算(性能极差)
  // const result = fibonacci(n); 

  // 优化后:仅当 n 变化时计算
  const result = useMemo(() => fibonacci(n), [n]);
  return <div>{result}</div>;
};

2. 避免子组件不必要的渲染

场景 :父组件传递引用类型(对象、数组、函数)给子组件时,避免因引用变化导致子组件重新渲染。
示例:传递配置对象

jsx 复制代码
const Child = React.memo(({ config }) => { /* ... */ });

const Parent = () => {
  const [count, setCount] = useState(0);

  // 未优化:每次渲染生成新对象,导致 Child 重新渲染
  // const config = { color: "red", size: 10 };

  // 优化后:仅当依赖项变化时生成新对象
  const config = useMemo(() => ({ color: "red", size: 10 }), []);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Update Parent</button>
      <Child config={config} />
    </div>
  );
};

3. 优化 Context 传递的值

场景 :当 Context 提供的数据是动态生成的引用类型时,避免消费者组件因引用变化而重新渲染。
示例:动态主题配置

jsx 复制代码
const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
  const [darkMode, setDarkMode] = useState(false);

  // 未优化:每次渲染生成新对象,导致所有消费者重新渲染
  // const theme = { darkMode, colors: darkMode ? darkColors : lightColors };

  // 优化后:仅当 darkMode 变化时生成新对象
  const theme = useMemo(() => ({
    darkMode,
    colors: darkMode ? darkColors : lightColors
  }), [darkMode]);

  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
};

4. 格式化或数据处理

场景 :数据转换(如日期格式化、国际化、数据聚合)。
示例:日期格式化

jsx 复制代码
const formatDate = (date) => {
  // 复杂的格式化逻辑
  return new Intl.DateTimeFormat('en-US').format(date);
};

const Component = ({ timestamp }) => {
  // 未优化:每次渲染都格式化
  // const formattedDate = formatDate(timestamp);

  // 优化后:仅当 timestamp 变化时格式化
  const formattedDate = useMemo(() => formatDate(timestamp), [timestamp]);
  return <div>{formattedDate}</div>;
};

5. 动态样式生成

场景 :根据状态生成复杂 CSS 样式对象。
示例:动态样式计算

jsx 复制代码
const Component = ({ isActive }) => {
  // 未优化:每次渲染生成新样式对象
  // const style = { 
  //   color: isActive ? 'red' : 'black',
  //   transform: `scale(${isActive ? 1.2 : 1})`
  // };

  // 优化后:仅当 isActive 变化时生成新对象
  const style = useMemo(() => ({
    color: isActive ? 'red' : 'black',
    transform: `scale(${isActive ? 1.2 : 1})`
  }), [isActive]);

  return <div style={style}>Dynamic Style</div>;
};

6. 大型列表或表格中的行渲染

场景 :渲染包含复杂行数据的列表时,避免每次渲染重新计算行数据。
示例:表格行数据预处理

jsx 复制代码
const Table = ({ data, sortKey }) => {
  // 未优化:每次渲染都重新排序
  // const sortedData = data.sort((a, b) => a[sortKey] - b[sortKey]);

  // 优化后:仅当 data 或 sortKey 变化时排序
  const sortedData = useMemo(() => 
    [...data].sort((a, b) => a[sortKey] - b[sortKey]), 
    [data, sortKey]
  );

  return (
    <table>
      {sortedData.map(row => <TableRow key={row.id} data={row} />)}
    </table>
  );
};

7. 避免重复渲染中的副作用

场景 :当某个值需要传递给副作用(如 useEffect)且需要稳定引用时。
示例:依赖稳定引用的副作用

jsx 复制代码
const Component = ({ id }) => {
  // 未优化:每次渲染生成新配置对象,导致 useEffect 重复执行
  // const config = { id, type: "FETCH" };

  // 优化后:仅当 id 变化时生成新配置
  const config = useMemo(() => ({ id, type: "FETCH" }), [id]);

  useEffect(() => {
    fetchData(config);
  }, [config]);

  return <div>...</div>;
};

8. 动态导入组件(Code Splitting)

场景 :按需加载组件时,避免重复动态导入。
示例:动态加载弹窗组件

jsx 复制代码
const LazyModal = React.lazy(() => import('./Modal'));

const Component = ({ showModal }) => {
  // 未优化:每次渲染都重新生成 JSX
  // const modal = showModal ? <LazyModal /> : null;

  // 优化后:仅在 showModal 变化时重新渲染
  const modal = useMemo(() => 
    showModal ? <LazyModal /> : null, 
    [showModal]
  );

  return <div>{modal}</div>;
};

9. 避免闭包中的过时值

场景 :在回调函数中需要访问最新状态时,结合 useRef 使用。
示例:定时器中的最新状态

jsx 复制代码
const Component = () => {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  // 使用 useMemo 缓存回调函数,避免闭包陷阱
  const updateCount = useMemo(() => () => {
    console.log("Current count:", countRef.current);
  }, []);

  useEffect(() => {
    countRef.current = count; // 同步最新值到 ref
  }, [count]);

  useEffect(() => {
    const timer = setInterval(updateCount, 1000);
    return () => clearInterval(timer);
  }, [updateCount]);

  return <button onClick={() => setCount(c => c + 1)}>Increment</button>;
};

10. 优化自定义 Hook 的输出

场景 :自定义 Hook 返回复杂数据时,避免调用组件不必要的更新。
示例:自定义数据获取 Hook

jsx 复制代码
const useFetchData = (url) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(url).then(res => res.json()).then(setData);
  }, [url]);

  // 未优化:每次返回新对象,导致使用该 Hook 的组件重新渲染
  // return { data, isLoading: !data };

  // 优化后:稳定返回值引用
  return useMemo(() => ({ data, isLoading: !data }), [data]);
};

三、useMemo 的误用场景

1. 简单计算

jsx 复制代码
// ❌ 误用:计算过于简单,反而增加内存开销
const sum = useMemo(() => a + b, [a, b]);

// ✅ 正确:直接计算
const sum = a + b;

2. 依赖项不完整

jsx 复制代码
// ❌ 误用:依赖项缺失,导致缓存值过时
const result = useMemo(() => a + b, [a]);

// ✅ 正确:包含所有依赖项
const result = useMemo(() => a + b, [a, b]);

3. 滥用引用稳定

jsx 复制代码
// ❌ 误用:即使不需要稳定引用,也强制缓存
const handleClick = useMemo(() => () => { /* ... */ }, []);

// ✅ 正确:优先使用 useCallback
const handleClick = useCallback(() => { /* ... */ }, []);

四、最佳实践总结

  1. 按需优化 :先用 Profiler 定位性能瓶颈,再针对性使用 useMemo
  2. 权衡成本:计算成本 < 缓存开销时,避免使用。
  3. 依赖项精确:确保依赖项数组包含所有变化的变量。
  4. 避免嵌套滥用 :过度使用 useMemo 可能导致代码可读性下降。

通过合理应用 useMemo,可以有效减少不必要的计算和渲染,提升 React 应用性能。核心原则是:在计算成本高或需要稳定引用时使用,避免在简单场景中滥用

相关推荐
前端小巷子8 分钟前
CSS单位完全指南
前端·css
SunTecTec1 小时前
Flink Docker Application Mode 命令解析 - 修改命令以启用 Web UI
大数据·前端·docker·flink
拉不动的猪2 小时前
前端常见数组分析
前端·javascript·面试
小吕学编程2 小时前
ES练习册
java·前端·elasticsearch
Asthenia04122 小时前
Netty编解码器详解与实战
前端
袁煦丞2 小时前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛3 小时前
vue组件间通信
前端·javascript·vue.js
一笑code3 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员4 小时前
layui时间范围
前端·javascript·layui
NoneCoder4 小时前
HTML响应式网页设计与跨平台适配
前端·html