React useCallback Hook 快速指南

2025-06-08

React useCallback Hook 快速指南

React useCallback 钩子可以帮助你提升 React 应用的性能。奇怪的是,useCallback 钩子却很少被提及。在本教程中,你将了解 React useCallback 是什么、它是如何工作的以及如何使用它。你还将学习一些关于 memoization 的知识。

React useCallback hook 简介

React useCallback 钩子的主要目的是记忆函数。这样做的主要原因是提高 React 应用程序的性能。这两者之间有什么关系呢?每次组件重新渲染时,它都会重新创建内部定义的函数。记忆函数可以帮助你避免这种情况。

当你使用 useCallback hook记忆一个函数时,该函数实际上会被存储在缓存中。举个简单的例子。假设某个事件导致你的组件重新渲染。假设状态发生了变化。通常,这种重新渲染默认也会导致 React 重新创建组件中定义的所有函数。

使用 useCallback hook 和 memoization 可能不会发生这种情况。当你 memoize 一个函数时,React 可能不会仅仅因为组件重新渲染就重新创建该函数。相反,React 可以跳过重新创建并返回 memoized 的函数。这可以帮助你节省资源和时间,并提高应用程序的性能。

useCallback hook 的语法

如果你已经了解 React useEffect hook,你会发现 useCallback 的语法很熟悉。它们实际上几乎相同。与 useEffect hook 类似,useCallback 也接受两个参数。第一个参数是你想要记忆的函数。第二个参数是一个依赖项数组。

这个依赖项数组指定了 React 应该监视的值。当这些值中的任何一个发生变化时,React 都应该重新创建该函数。否则,它应该返回该函数的记忆版本。

// Import useCallback hook from React:
import { useCallback } from 'react'

export default function App() {
  // Use useCallback to memoize function:
  const memoizedFunc = useCallback(() => {
    someFunction() // Function that will be memoized.
  }, [/* depOne, depTwo, ...dep */]) // <= Dependency array.

  // A bit shorter version:
  const memoizedFunc = useCallback(() => someFunction(), [])

  return (
    <div className="App">
      {/* Your component */}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

依赖的力量

依赖项数组很重要。它帮助 React 理解何时返回已记忆的函数以及何时重新创建它。为什么要重新创建它?记忆的目的不就是防止这种情况发生吗?嗯,既是又不是。是的,你想要防止函数被重新创建。

但是,如果函数依赖于某些输入,则需要在输入发生变化时重新创建该函数。否则,您将使用不再相关的旧输入来执行该函数。例如,假设您有一个使用用户姓名问候用户的函数。

此函数依赖于当前用户的名称。如果您在首次创建时将其记忆,它将记住名字。当名称更改时,它将不会记录该名称。它将使用名字来迎接每个后续用户。解决方案是将名称添加为依赖项。

当你将名称指定为依赖项时,React 会在名称更改时自动重新创建该函数。当新用户加入且名称更改时,该函数也会重新创建。它会更新其输入,使用名称的最新值,并使用正确的名称向用户致意。

一个简单的例子

让我们通过一个简单的例子来演示依赖和记忆化的强大功能。假设你有一个简单的组件,它包含输入框和按钮。输入框允许用户指定她的名字。这个名字将存储在使用useState hook创建的本地状态中。点击按钮会将名字打印到控制台。

按钮的处理函数将通过 useCallback hook 进行记忆。第一次尝试时,你忘记将名称作为 hook 的依赖项。你需要做的是将依赖项数组指定为一个空数组。这会告诉 React 应该仅在初始渲染时创建该函数。

当发生导致组件后续重新渲染的情况时,它应该返回函数的记忆版本。请记住,更改状态会导致 React 重新渲染。这有助于保持所有内容同步。当用户在输入框中输入姓名并点击按钮时会发生什么?

用户可能会感到惊讶。控制台会显示“name”状态的初始值。这是因为在函数创建时,name 的值就是初始值。当 name 更改时,React 并没有重新创建该函数,而函数并不知道 name 已经更改。

// Note: this will not work as you may expect:
// Import useCallback and useState hooks from React.
import { useCallback, useState } from 'react'

export default function App() {
  // Create state for name:
  const [name, setName] = useState('')

  // Create and memoize function for logging name:
  const handleShowName = useCallback(() => {
    console.log(name)
  }, []) // <= Notice the empty array with dependencies.

  // Each click on the button will log
  // the initial value of "name" state, i.e. the ''.

  return (
    <div className="App">
      {/* Change "name" state when input changes: */}
      <input value={name} onChange={(event) => setName(event.target.value)} />

      {/* Attach handleShowName function */}
      <button onClick={handleShowName}>Show name</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

解决这个问题的一个简单方法是将“name”状态添加为依赖项。现在,React 将监视此值,并在名称更改时重新创建该函数。这将确保当用户更改名称时,该函数始终具有最新信息并记录正确的值。

// Note: this will not work as you may expect:
import { useCallback, useState } from 'react'

export default function App() {
  // Create state for name
  const [name, setName] = useState('')

  // Create and memoize function for logging name:
  const handleShowName = useCallback(() => {
    console.log(name)
  }, [name]) // <= Add "name" state as dependency.

  return (
    <div className="App">
      {/* Change name state when input changes: */}
      <input value={name} onChange={(event) => setName(event.target.value)} />

      {/* Attach handleShowName function */}
      <button onClick={handleShowName}>Show name</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

处理依赖项以及何时重新创建记忆函数

依赖项数组(第二个参数)告诉 React 何时应重新创建记忆函数。基本上有三种选择。

每次渲染后

首先,React 会在每次组件渲染后重新创建该函数。这几乎违背了 useCallback hook 的初衷,但你仍然可以这么做。为此,你只需省略依赖项数组即可。仅将 useCallback hook 与你想要记忆的函数一起使用。

// Import useCallback hook from React:
import { useCallback } from 'react'

export default function App() {
  // Use useCallback to memoize function:
  const memoizedFunc = useCallback(() => someFunction())
  // Omit the dependency parameter (array).

  return (
    <div className="App">
      {/* Your component */}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

如果你确实想这么做,可以直接跳过 useCallback hook。此选项与声明一个不使用 useCallback hook 的函数效果相同。该函数将在每次重新渲染时重新创建,并且不会被记忆。

// Import useCallback hook from React:
import { useCallback } from 'react'

export default function App() {
  // Normal function:
  const someFunction = () => (/* Do something */)

  return (
    <div className="App">
      {/* Your component */}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

仅在初始渲染后

第二种选择是仅在初始渲染后创建函数。当后续重新渲染时,React 将返回该函数的记忆版本。这在两种情况下很有用。首先,当函数应该始终返回相同的结果,并且可能无法处理外部输入时。

第二种情况是函数需要处理外部输入,但输入不会改变。如果输入不会改变,或者函数不依赖任何外部输入,你可以考虑将其记忆化。为此,请传递一个空数组作为依赖项参数。

// Import useCallback hook from React:
import { useCallback } from 'react'

export default function App() {
  // Use useCallback to memoize function:
  const memoizedFunc = useCallback(() => someFunction(), [])
  // Pass an empty array as dependency parameter.

  return (
    <div className="App">
      {/* Your component */}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

当特定值发生变化时

最后一个选项是当只有特定值或值发生变化时重新创建函数。如果某些值发生变化,React 将重新创建函数以确保其拥有最新数据。否则,它将返回函数的记忆版本。为此,请在依赖项数组中指定要监视的值作为参数。

从现在开始,当任何这些被监视的值发生变化时,React 都会自动重新创建该函数。否则,它将返回已记忆的版本。请记住,只需更改一个指定为依赖项的值,React 就会重新创建该函数,而不是所有值都更改。

// Import useCallback hook from React:
import { useCallback, useState } from 'react'

export default function App() {
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [isValid, setIsValid] = useState(false)

  // Create and memoize form handler
  const handleFormSubmit = useCallback(
    () => {
      // Submit form.
    },
    [name, email, isValid], // <= Watch "name", "email" and "isValid".
  )

  return (
    <form className="App">
      {/* Your form component */}

      <button onClick={handleFormSubmit}></button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

警告

仅仅因为有某个工具并不意味着你必须使用它。这同样适用于 React useCallback 钩子。此钩子的目的是提高重型组件的性能。它并非旨在成为你在组件中声明的每个函数的默认“包装器”。

所以,不要想当然地认为每次声明函数时都必须使用 useCallback。其实不需要。在使用多个函数的重型组件中使用此钩子,这些函数不必在每次渲染时都重新创建。即便如此,也要考虑潜在的利弊。

记忆化能显著提升性能吗?还是说,它只会增加代码的复杂性,而性能提升几乎难以察觉?对于小型轻量级的组件来说,useCallback 可能没什么用。

结论:React useCallback hook 快速指南

React useCallback 钩子可以有效地提升应用的性能,因为它可以存储函数以供后续使用,而无需在每次重新渲染时重新创建它们。这可以改善重渲染行为并提升大型组件的性能。希望本教程能帮助您理解 useCallback 钩子的工作原理以及如何使用它。

鏂囩珷鏉ユ簮锛�https://dev.to/alexdevero/a-quick-guide-to-react-usecallback-hook-47dn
PREV
JavaScript Fetch API 入门
NEXT
创建良好 JavaScript 变量的 7 种做法