My App

useCallback 钩子

useCallback 钩子

好的,那差不多涵盖了 useMemo … 那 useCallback 呢?

这里是简短版本:它解决了与 useMemo 相同的“保留引用”问题,但针对的是函数而不是数组/对象。

与数组和对象类似,函数是通过引用而不是通过值进行比较的:

const functionOne = function () {
  return 5;
};
const functionTwo = function () {
  return 5;
};
 
console.log(functionOne === functionTwo); // false

这意味着如果我们在组件内定义一个函数,它将在每次渲染时被重新生成,每次都会产生一个相同但独特的函数。

让我们看一个例子:

import React from 'react';

import MegaBoost from './MegaBoost';

function App() {
  const [count, setCount] = React.useState(0);

  function handleMegaBoost() {
    setCount((currentValue) => currentValue + 1234);
  }

  return (
    <>
      Count: {count}
      <button
        onClick={() => {
          setCount(count + 1)
        }}
      >
        Click me!
      </button>
      <MegaBoost handleClick={handleMegaBoost} />
    </>
  );
}

export default App;

这个沙盒展示了一个典型的计数应用,但有一个特殊的“超级增强”按钮。这个按钮可以大量增加计数,以备你急需时不想多次点击标准按钮。

MegaBoost 组件是一个纯组件,这要感谢 React.memo (你会发现它包裹在 MegaBoost.js 内的默认导出中)。这个组件不接收 count 作为属性,但每当 count 变化时,它会重新渲染!

这里的问题是我们在每次渲染时都会生成一个全新的函数。如果我们渲染 3 次,就会创建 3 个独立的 handleMegaBoost 函数,从而突破 React.memo 势场。

这是我们在上一课中看到的 boxes 数组的相同问题,但这次传递的是一个双重函数作为属性,而不是一个双重数组。

利用我们对 useMemo 的了解,我们可以这样解决问题:

const handleMegaBoost = React.useMemo(() => {
  return function () {
    setCount((currentValue) => currentValue + 1234);
  };
}, []);

我们不是返回一个数组,而是返回一个函数。这个函数随后被存储在 handleMegaBoost 变量中。

这可以工作……但有一种更传统的方法来做到这一点:

const handleMegaBoost = React.useCallback(() => {
  setCount((currentValue) => currentValue + 1234);
}, []);

useCallback 的作用与 useMemo 相同,但它是专门为函数构建的。我们直接将一个函数传递给它,它会对该函数进行记忆,并在渲染之间进行处理。

换句话说,这两个表达式具有相同的效果:

// This:
React.useCallback(function helloWorld() {}, []);
 
// ...Is functionally equivalent to this:
React.useMemo(() => function helloWorld() {}, []);

从本质上讲, useCallback 是语法糖*吗?它的存在纯粹是为了在尝试记忆回调函数时让我们的生活变得更美好。

状态设置回调

在这个例子中,我们使用了更新状态变量的替代语法。如果您想复习一下这个语法,请查看“状态设置回调”课程。

我们为什么在这里使用它?好吧,让我们考虑一下如果我们使用标准语法会发生什么:

const handleMegaBoost = React.useCallback(() => {
  setCount(count + 1234);
 
  // ⚠️ ESLint warning ⚠️
  // `count` is missing from the dependency array:
}, []);

我们正在尝试访问 count ,但我们没有将其列为依赖项。正如我们在效果中看到的,这意味着 count 将变得过时;这个函数内部的 count 变量将始终等于 0 ,无论真实的 count 值发生什么。这实际上使这个按钮失效。

我们可以通过将 count 添加到依赖数组来修复这个问题:

const handleMegaBoost = React.useCallback(() => {
  setCount(count + 1234);
}, [count]);

这有效,但这也意味着每当 count 发生变化时, handleMegaBoost 将被重新生成。这意味着我们的 MegaBoost 组件将在每次 count 更新时重新渲染,这正是我们想要避免的。

这种替代语法使我们能够在 count 变化时访问 count 状态的最新值,而无需每次都生成一个新函数。我们可以兼得两全其美。

这东西很难!

在学习 React 时,许多开发者在适应 useMemo 和 useCallback 方面感到困难。这很难!

但好消息是:您可以在不使用这两个钩子的情况下构建复杂的、可投入生产的应用程序!

这些钩子旨在帮助高级开发者优化他们的应用程序,但 React 自带的性能已经相当快,而人们使用的设备也持续变得越来越快。

如果你在这些“记忆化”课程中的概念上一直挣扎,我鼓励你暂时将它们放在一边,继续学习课程。几个月后,当你对 React 更加熟悉时,可以再回来看看。

换句话说,这些课程不应该是“阻碍”。现在可以随意继续!