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>
)
}
依赖的力量
依赖项数组很重要。它帮助 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>
)
}
解决这个问题的一个简单方法是将“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>
)
}
处理依赖项以及何时重新创建记忆函数
依赖项数组(第二个参数)告诉 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>
)
}
如果你确实想这么做,可以直接跳过 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>
)
}
仅在初始渲染后
第二种选择是仅在初始渲染后创建函数。当后续重新渲染时,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>
)
}
当特定值发生变化时
最后一个选项是当只有特定值或值发生变化时重新创建函数。如果某些值发生变化,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>
)
}
警告
仅仅因为有某个工具并不意味着你必须使用它。这同样适用于 React useCallback 钩子。此钩子的目的是提高重型组件的性能。它并非旨在成为你在组件中声明的每个函数的默认“包装器”。
所以,不要想当然地认为每次声明函数时都必须使用 useCallback。其实不需要。在使用多个函数的重型组件中使用此钩子,这些函数不必在每次渲染时都重新创建。即便如此,也要考虑潜在的利弊。
记忆化能显著提升性能吗?还是说,它只会增加代码的复杂性,而性能提升几乎难以察觉?对于小型轻量级的组件来说,useCallback 可能没什么用。
结论:React useCallback hook 快速指南
React useCallback 钩子可以有效地提升应用的性能,因为它可以存储函数以供后续使用,而无需在每次重新渲染时重新创建它们。这可以改善重渲染行为并提升大型组件的性能。希望本教程能帮助您理解 useCallback 钩子的工作原理以及如何使用它。
鏂囩珷鏉ユ簮锛�https://dev.to/alexdevero/a-quick-guide-to-react-usecallback-hook-47dn