不要再代码中滥用 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 缓存的是计算结果
  • 我们应该只在少场景下使用他们,不能滥用(展开讲讲,参考上文)

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

相关推荐
HBR666_8 分钟前
vue3 excel文件导入
前端·excel
天天扭码12 分钟前
偶遇天才算法题 | 拼劲全力,无法战胜 😓
前端·算法·面试
小菜刀刀16 分钟前
文件包含漏洞,目录遍历漏洞,CSRF,SSRF
前端·csrf
anyup_前端梦工厂34 分钟前
React 单一职责原则:优化组件设计与提高可维护性
前端·javascript·react.js
天天扭码1 小时前
面试官:算法题”除自身以外数组的乘积“ 我:😄 面试官:不能用除法 我:😓
前端·算法·面试
小小小小宇1 小时前
十万字JS不良实践总结(逼疯审核版)
前端
喝拿铁写前端1 小时前
从列表页到规则引擎:一个组件封装过程中的前端认知进阶
前端·vue.js·架构
小小小小宇1 小时前
React Lanes(泳道)机制
前端
zhangxingchao1 小时前
Jetpack Compose 之 Modifier(上)
前端
龙萌酱2 小时前
力扣每日打卡17 49. 字母异位词分组 (中等)
前端·javascript·算法·leetcode