不要再代码中滥用 useCallback 和useMemo

初学 React 的同学有时候会一头雾水,尤其是Hooks。 有时候会发现用不用它都能达到效果。这时候就很容易滥用 Hook 的场景。(这也可能只是我自己初学 React 的时候遇到的问题)。 所以打算捋一捋这些 Hook,到底什么时候才使用呢?先说一说 useCallbackuseMemo 这两个,因为这两个总是会被面试官问到区别和用法。

useCallback

useCallback 是一个允许你再多次渲染中缓存函数的Hook。

精简一下:缓存函数

用法:

ini 复制代码
const cachedFn = useCallback(fn, dependencies)

fn: 想要缓存的函数。

dependencies :依赖项;当组件重新(不是首次) 渲染时,会调用 Object.is 判断依赖项是否发生变化;如果依赖项没有变化的时候,则 useCallback 回返回相同的函数。

注意

  • 首先这是一个 Hook ,所以要遵循Hook的规则,只能在顶层中使用,不能在循环或者条件语句中调用。
  • 我们看到 useCallback 的概念,可以知道他是用来缓存函数 的,所以这个Hook 是一种优化手段,避免组件重新渲染时重复创建相同的函数,从而优化性能。

什么时候用?

使用 useCallback 缓存函数仅在少数情况下有意义:

  1. 将其作为 props 传递给包装在 [memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。 举个例子:
javascript 复制代码
function ProductPage({ productId, referrer, theme }) {
  
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);
  
  // ...
  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );


const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ...
});

首先我们知道,默认情况下,当一个组件重新渲染时, React 将递归渲染 它的所有子组件。如果使用 memo 包裹子组件,如果props相同,子组件将不会重新渲染;

分析下上面这段代码:

handleSubmit 作为参数传递给了子组件。 如果此时 handleSubmit 没有使用 useCallback 进行缓存, 那当 theme 更新之后,组件重新渲染,也就意味着函数重新执行,handleSubmit也就重新创建了,这会导致传递给子组件的props永远都不会是相同, 此时 memo 的性能优化永远也不会生效

  1. 传递的函数可能作为某些 Hook 的依赖。比如,另一个包裹在 useCallback 中的函数依赖于它,或者依赖于 useEffect中的函数。 举个例子:
scss 复制代码
import React, { useEffect, useState, useCallback } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  // 使用 useCallback,确保 handleClick 函数在 count 不变时不重新创建
  const handleClick = useCallback(() => {
    console.log("Clicked", count);
  }, [count]);  // 仅在 count 变化时更新 handleClick

  // 使用 useEffect,依赖 handleClick 函数
  useEffect(() => {
    console.log('Effect triggered!');
    handleClick();
  }, [handleClick]);  // 现在只有 handleClick 变化时,useEffect 才会重新执行

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

在这个例子中,useEffect 依赖于 handleClick 函数。每当 handleClick 发生变化时,useEffect 会重新执行。如果handleClick 不被 useCallback 包裹每次渲染时都会被重新创建,因此 useEffect 也会被多次调用。

其他情况下,函数包装在useCallback没有任何意义!!! ;谨记!!

其他情况下,函数包装在useCallback没有任何意义!!! ;谨记!!

其他情况下,函数包装在useCallback没有任何意义!!! ;谨记!!

清楚了useCallback之后,useMemo 也就很好理解了。

useMemo

useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。

精简一下:缓存结果

用法:

ini 复制代码
const cachedValue = useMemo(calculateValue, dependencies)

calculateValue: 缓存的值。

dependencies :依赖项;当组件重新(不是首次) 渲染时,会调用 Object.is 判断依赖项是否发生变化;如果依赖项没有变化的时候,则 useMemo 回返回相同的值。

什么时候用?

使用 useMemo 进行优化仅在少数情况下有价值:

  • (和 useCallback 提到的两个场景相似, 只不过把函数 换成了);
  • 你在 useMemo 中进行的计算明显很慢 ,而且它的依赖关系很少改变
javascript 复制代码
const ExpensiveComponent = ({ data }) => {
  // 假设计算非常复杂,可能需要几秒钟
  const expensiveCalculation = (data) => {
    console.log("Recomputing expensive calculation...");
    return data.reduce((acc, num) => acc + num, 0);  // 假设这是个复杂的计算
  };

  // 使用 useMemo 只在 data 改变时重新计算
  const memoizedResult = useMemo(() => expensiveCalculation(data), [data]);

  return <div>Result: {memoizedResult}</div>;
};

export default ExpensiveComponent;

注意:这句话中提到了两点,上面代码说明了计算很慢的场景, 还有一点就是依赖关系很少改变,依赖频繁变化,就没有缓存的意义了,此时使用 useMemo 还带来了额外的开销,因为本身执行的时候也是需要开销的。

总结

  • 首先useCallbackuseMemo 都是 React 中的优化 Hook,都是只应作用于性能优化
  • useCallback 缓存的是函数
  • useMemo 缓存的是计算结果
  • 我们应该只在少场景下使用他们,不能滥用(展开讲讲,参考上文)

文中有不正确的地方多谢指正。

相关推荐
Jagger_4 分钟前
Cursor + Apifox MCP:告别手动复制接口,AI 助你高效完成接口文档开发
前端
IT_陈寒20 分钟前
Redis性能优化:5个被低估的配置项让你的QPS提升50%
前端·人工智能·后端
Hilaku27 分钟前
重新思考CSS Reset:normalize.css vs reset.css vs remedy.css,在2025年该如何选?
前端·css·代码规范
袁煦丞40 分钟前
一图看懂Docker管理 Portainer:cpoar内网穿透实验室第652个成功挑战
前端·程序员·远程工作
右子1 小时前
微信小程序开发“闭坑”指南
前端·javascript·微信小程序
入秋1 小时前
Three.js后期处理实战:噪点 景深 以及色彩调整
前端·javascript·three.js
Asort1 小时前
JavaScript设计模式(七)——桥接模式:解耦抽象与实现的优雅之道
前端·javascript·设计模式
golang学习记1 小时前
从0死磕全栈之Next.js 应用中的认证与授权:从零实现安全用户系统
前端
苏打水com2 小时前
携程前端业务:在线旅游生态下的「复杂行程交互」与「高并发预订」实践
前端·状态模式·旅游
Darenm1112 小时前
深入理解CSS BFC:块级格式化上下文
前端·css