diff --git a/src/content/reference/react/useCallback.md b/src/content/reference/react/useCallback.md index 8f9751afa9..9fcd20f4d8 100644 --- a/src/content/reference/react/useCallback.md +++ b/src/content/reference/react/useCallback.md @@ -4,7 +4,7 @@ title: useCallback -`useCallback` is a React Hook that lets you cache a function definition between re-renders. +`useCallback` 是一个允许你在多次渲染中缓存函数的 React Hook。 ```js const cachedFn = useCallback(fn, dependencies) @@ -16,11 +16,11 @@ const cachedFn = useCallback(fn, dependencies) --- -## Reference {/*reference*/} +## 参考 {/*reference*/} ### `useCallback(fn, dependencies)` {/*usecallback*/} -Call `useCallback` at the top level of your component to cache a function definition between re-renders: +在组件顶层调用 `useCallback` 以便在多次渲染中缓存函数: ```js {4,9} import { useCallback } from 'react'; @@ -34,34 +34,34 @@ export default function ProductPage({ productId, referrer, theme }) { }, [productId, referrer]); ``` -[See more examples below.](#usage) +[参见下面更多示例](#usage)。 -#### Parameters {/*parameters*/} +#### 参数 {/*parameters*/} -* `fn`: The function value that you want to cache. It can take any arguments and return any values. React will return (not call!) your function back to you during the initial render. On next renders, React will give you the same function again if the `dependencies` have not changed since the last render. Otherwise, it will give you the function that you have passed during the current render, and store it in case it can be reused later. React will not call your function. The function is returned to you so you can decide when and whether to call it. +* `fn`:想要缓存的函数。此函数可以接受任何参数并且返回任何值。React 将会在初次渲染而非调用时返回该函数。当进行下一次渲染时,如果 `dependencies` 相比于上一次渲染时没有改变,那么 React 将会返回相同的函数。否则,React 将返回在最新一次渲染中传入的函数,并且将其缓存以便之后使用。React 不会调用此函数,而是返回此函数。你可以自己决定何时调用以及是否调用。 -* `dependencies`: The list of all reactive values referenced inside of the `fn` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. +* `dependencies`:有关是否更新 `fn` 的所有响应式值的一个列表。响应式值包括 props、state,和所有在你组件内部直接声明的变量和函数。如果你的代码检查工具 [配置了 React](/learn/editor-setup#linting),那么它将校验每一个正确指定为依赖的响应式值。依赖列表必须具有确切数量的项,并且必须像 `[dep1, dep2, dep3]` 这样编写。React 使用 [`Object.is`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is) 比较每一个依赖和它的之前的值。 -#### Returns {/*returns*/} +#### 返回值 {/*returns*/} -On the initial render, `useCallback` returns the `fn` function you have passed. +在初次渲染时,`useCallback` 返回你已经传入的 `fn` 函数 -During subsequent renders, it will either return an already stored `fn` function from the last render (if the dependencies haven't changed), or return the `fn` function you have passed during this render. +在之后的渲染中, 如果依赖没有改变,`useCallback` 返回上一次渲染中缓存的 `fn` 函数;否则返回这一次渲染传入的 `fn`。 -#### Caveats {/*caveats*/} +#### 注意 {/*caveats*/} -* `useCallback` is a Hook, so you can only call it **at the top level of your component** or your own Hooks. You can't call it inside loops or conditions. If you need that, extract a new component and move the state into it. -* React **will not throw away the cached function unless there is a specific reason to do that.** For example, in development, React throws away the cache when you edit the file of your component. Both in development and in production, React will throw away the cache if your component suspends during the initial mount. In the future, React may add more features that take advantage of throwing away the cache--for example, if React adds built-in support for virtualized lists in the future, it would make sense to throw away the cache for items that scroll out of the virtualized table viewport. This should match your expectations if you rely on `useCallback` as a performance optimization. Otherwise, a [state variable](/reference/react/useState#im-trying-to-set-state-to-a-function-but-it-gets-called-instead) or a [ref](/reference/react/useRef#avoiding-recreating-the-ref-contents) may be more appropriate. +* `useCallback` 是一个 Hook,所以应该在 **组件的顶层** 或自定义 Hook 中调用。你不应在循环或者条件语句中调用它。如果你需要这样做,请新建一个组件,并将 state 移入其中。 +* 除非有特定的理由,React **将不会丢弃已缓存的函数**。例如,在开发中,当编辑组件文件时,React 会丢弃缓存。在生产和开发环境中,如果你的组件在初次挂载中暂停,React 将会丢弃缓存。在未来,React 可能会增加更多利用了丢弃缓存机制的特性。例如,如果 React 未来内置了对虚拟列表的支持,那么在滚动超出虚拟化表视口的项目时,抛弃缓存是有意义的。如果你依赖 `useCallback` 作为一个性能优化途径,那么这些对你会有帮助。否则请考虑使用 [state 变量](/reference/react/useState#im-trying-to-set-state-to-a-function-but-it-gets-called-instead) 或 [ref](/reference/react/useRef#avoiding-recreating-the-ref-contents)。 --- -## Usage {/*usage*/} +## 用法 {/*usage*/} -### Skipping re-rendering of components {/*skipping-re-rendering-of-components*/} +### 跳过组件的重新渲染 {/*skipping-re-rendering-of-components*/} -When you optimize rendering performance, you will sometimes need to cache the functions that you pass to child components. Let's first look at the syntax for how to do this, and then see in which cases it's useful. +当你优化渲染性能的时候,有时需要缓存传递给子组件的函数。让我们先关注一下如何实现,稍后去理解在哪些场景中它是有用的。 -To cache a function between re-renders of your component, wrap its definition into the `useCallback` Hook: +为了缓存组件中多次渲染的函数,你需要将其定义在 `useCallback` Hook 中: ```js [[3, 4, "handleSubmit"], [2, 9, "[productId, referrer]"]] import { useCallback } from 'react'; @@ -76,20 +76,20 @@ function ProductPage({ productId, referrer, theme }) { // ... ``` -You need to pass two things to `useCallback`: +你需要传递两个参数给 `useCallback`: -1. A function definition that you want to cache between re-renders. -2. A list of dependencies including every value within your component that's used inside your function. +1. 在多次渲染中需要缓存的函数 +2. 函数内部需要使用到的所有组件内部值的 依赖列表。 -On the initial render, the returned function you'll get from `useCallback` will be the function you passed. +初次渲染时,在 `useCallback` 处接收的 返回函数 将会是已经传入的函数。 -On the following renders, React will compare the dependencies with the dependencies you passed during the previous render. If none of the dependencies have changed (compared with [`Object.is`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), `useCallback` will return the same function as before. Otherwise, `useCallback` will return the function you passed on *this* render. +在之后的渲染中,React 将会使用 [`Object.is`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is) 把 当前的依赖 和已传入之前的依赖进行比较。如果没有任何依赖改变,`useCallback` 将会返回与之前一样的函数。否则 `useCallback` 将返回 **此次** 渲染中传递的函数。 -In other words, `useCallback` caches a function between re-renders until its dependencies change. +简而言之,`useCallback` 在多次渲染中缓存一个函数,直至这个函数的依赖发生改变。 -**Let's walk through an example to see when this is useful.** +**让我们通过一个示例看看它何时有用**。 -Say you're passing a `handleSubmit` function down from the `ProductPage` to the `ShippingForm` component: +假设你正在从 `ProductPage` 传递一个 `handleSubmit` 函数到 `ShippingForm` 组件中: ```js {5} function ProductPage({ productId, referrer, theme }) { @@ -101,9 +101,9 @@ function ProductPage({ productId, referrer, theme }) { ); ``` -You've noticed that toggling the `theme` prop freezes the app for a moment, but if you remove `` from your JSX, it feels fast. This tells you that it's worth trying to optimize the `ShippingForm` component. +注意,切换 `theme` props 后会让应用停滞一小会,但如果将 `` 从 JSX 中移除,应用将反应迅速。这就提示尽力优化 `ShippingForm` 组件将会很有用。 -**By default, when a component re-renders, React re-renders all of its children recursively.** This is why, when `ProductPage` re-renders with a different `theme`, the `ShippingForm` component *also* re-renders. This is fine for components that don't require much calculation to re-render. But if you verified a re-render is slow, you can tell `ShippingForm` to skip re-rendering when its props are the same as on last render by wrapping it in [`memo`:](/reference/react/memo) +**默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件**,因此每当因 `theme` 更改时而 `ProductPage` 组件重新渲染时,`ShippingForm` 组件也会重新渲染。这对于不需要大量计算去重新渲染的组件来说影响很小。但如果你发现某次重新渲染很慢,你可以将 `ShippingForm` 组件包裹在 [`memo`](/reference/react/memo) 中。如果 props 和上一次渲染时相同,那么 `ShippingForm` 组件将跳过重新渲染。 ```js {3,5} import { memo } from 'react'; @@ -113,11 +113,11 @@ const ShippingForm = memo(function ShippingForm({ onSubmit }) { }); ``` -**With this change, `ShippingForm` will skip re-rendering if all of its props are the *same* as on the last render.** This is when caching a function becomes important! Let's say you defined `handleSubmit` without `useCallback`: +**当代码像上面一样改变后,如果 props 与上一次渲染时相同,`ShippingForm` 将跳过重新渲染**。这时缓存函数就变得很重要。假设定义了 `handleSubmit` 而没有定义 `useCallback`: ```js {2,3,8,12-13} function ProductPage({ productId, referrer, theme }) { - // Every time the theme changes, this will be a different function... + // 每当 theme 改变时,都会生成一个不同的函数 function handleSubmit(orderDetails) { post('/product/' + productId + '/buy', { referrer, @@ -127,47 +127,47 @@ function ProductPage({ productId, referrer, theme }) { return (
- {/* ... so ShippingForm's props will never be the same, and it will re-render every time */} + {/* 这将导致 ShippingForm props 永远都不会是相同的,并且每次它都会重新渲染 */}
); } ``` -**In JavaScript, a `function () {}` or `() => {}` always creates a _different_ function,** similar to how the `{}` object literal always creates a new object. Normally, this wouldn't be a problem, but it means that `ShippingForm` props will never be the same, and your [`memo`](/reference/react/memo) optimization won't work. This is where `useCallback` comes in handy: +与字面量对象 `{}` 总是会创建新对象类似,**在 JavaScript 中,`function () {}` 或者 `() => {}` 总是会生成不同的函数**。正常情况下,这不会有问题,但是这意味着 `ShippingForm` props 将永远不会是相同的,并且 [`memo`](/reference/react/memo) 对性能的优化永远不会生效。而这就是 `useCallback` 起作用的地方: ```js {2,3,8,12-13} function ProductPage({ productId, referrer, theme }) { - // Tell React to cache your function between re-renders... + // 在多次渲染中缓存函数 const handleSubmit = useCallback((orderDetails) => { post('/product/' + productId + '/buy', { referrer, orderDetails, }); - }, [productId, referrer]); // ...so as long as these dependencies don't change... + }, [productId, referrer]); // 只要这些依赖没有改变 return (
- {/* ...ShippingForm will receive the same props and can skip re-rendering */} + {/* ShippingForm 就会收到同样的 props 并且跳过重新渲染 */}
); } ``` -**By wrapping `handleSubmit` in `useCallback`, you ensure that it's the *same* function between the re-renders** (until dependencies change). You don't *have to* wrap a function in `useCallback` unless you do it for some specific reason. In this example, the reason is that you pass it to a component wrapped in [`memo`,](/reference/react/memo) and this lets it skip re-rendering. There are other reasons you might need `useCallback` which are described further on this page. +**将 `handleSubmit` 传递给 `useCallback` 就可以确保它在多次重新渲染之间是相同的函数**,直到依赖发生改变。注意,除非出于某种特定原因,否则不必将一个函数包裹在 `useCallback` 中。在本例中,你将它传递到了包裹在 [`memo`](/reference/react/memo) 中的组件,这允许它跳过重新渲染。不过还有其他场景可能需要用到 `useCallback`,本章将对此进行进一步描述。 -**You should only rely on `useCallback` as a performance optimization.** If your code doesn't work without it, find the underlying problem and fix it first. Then you may add `useCallback` back. +**`useCallback` 只应作用于性能优化**。如果代码在没有它的情况下无法运行,请找到根本问题并首先修复它,然后再使用 `useCallback`。 -#### How is useCallback related to useMemo? {/*how-is-usecallback-related-to-usememo*/} +#### `useCallback` 与 `useMemo` 有何关系? {/*how-is-usecallback-related-to-usememo*/} -You will often see [`useMemo`](/reference/react/useMemo) alongside `useCallback`. They are both useful when you're trying to optimize a child component. They let you [memoize](https://en.wikipedia.org/wiki/Memoization) (or, in other words, cache) something you're passing down: +[`useMemo`](/reference/react/useMemo) 经常与 `useCallback` 一同出现。当尝试优化子组件时,它们都很有用。他们会 [记住](https://en.wikipedia.org/wiki/Memoization)(或者说,缓存)正在传递的东西: ```js {6-8,10-15,19} import { useMemo, useCallback } from 'react'; @@ -175,11 +175,11 @@ import { useMemo, useCallback } from 'react'; function ProductPage({ productId, referrer }) { const product = useData('/product/' + productId); - const requirements = useMemo(() => { // Calls your function and caches its result + const requirements = useMemo(() => { //调用函数并缓存结果 return computeRequirements(product); }, [product]); - const handleSubmit = useCallback((orderDetails) => { // Caches your function itself + const handleSubmit = useCallback((orderDetails) => { // 缓存函数本身 post('/product/' + productId + '/buy', { referrer, orderDetails, @@ -194,60 +194,60 @@ function ProductPage({ productId, referrer }) { } ``` -The difference is in *what* they're letting you cache: +区别在于你需要缓存 **什么**: -* **[`useMemo`](/reference/react/useMemo) caches the *result* of calling your function.** In this example, it caches the result of calling `computeRequirements(product)` so that it doesn't change unless `product` has changed. This lets you pass the `requirements` object down without unnecessarily re-rendering `ShippingForm`. When necessary, React will call the function you've passed during rendering to calculate the result. -* **`useCallback` caches *the function itself.*** Unlike `useMemo`, it does not call the function you provide. Instead, it caches the function you provided so that `handleSubmit` *itself* doesn't change unless `productId` or `referrer` has changed. This lets you pass the `handleSubmit` function down without unnecessarily re-rendering `ShippingForm`. Your code won't run until the user submits the form. +* **[`useMemo`](/reference/react/useMemo) 缓存函数调用的结果**。在这里,它缓存了调用 `computeRequirements(product)` 的结果。除非 `product` 发生改变,否则它将不会发生变化。这让你向下传递 `requirements` 时而无需不必要地重新渲染 `ShippingForm`。必要时,React 将会调用传入的函数重新计算结果。 +* **`useCallback` 缓存函数本身**。不像 `useMemo`,它不会调用你传入的函数。相反,它缓存此函数。从而除非 `productId` 或 `referrer` 发生改变,`handleSubmit` 自己将不会发生改变。这让你向下传递 `handleSubmit` 函数而无需不必要地重新渲染 `ShippingForm`。直至用户提交表单,你的代码都将不会运行。 -If you're already familiar with [`useMemo`,](/reference/react/useMemo) you might find it helpful to think of `useCallback` as this: +如果你已经熟悉了 [`useMemo`](/reference/react/useMemo),你可能发现将 `useCallback` 视为以下内容会很有帮助: ```js -// Simplified implementation (inside React) +// 在 React 内部的简化实现 function useCallback(fn, dependencies) { return useMemo(() => fn, dependencies); } ``` -[Read more about the difference between `useMemo` and `useCallback`.](/reference/react/useMemo#memoizing-a-function) +[阅读更多关于 `useMemo` 与 `useCallback` 之间区别的信息](/reference/react/useMemo#memoizing-a-function)。 -#### Should you add useCallback everywhere? {/*should-you-add-usecallback-everywhere*/} +#### 是否应该在任何地方添加 `useCallback`? {/*should-you-add-usecallback-everywhere*/} -If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful. +如果你的应用程序与本网站类似,并且大多数交互都很粗糙(例如替换页面或整个部分),则通常不需要缓存。另一方面,如果你的应用更像是一个绘图编辑器,并且大多数交互都是精细的(如移动形状),那么你可能会发现缓存非常有用。 -Caching a function with `useCallback` is only valuable in a few cases: +使用 `useCallback` 缓存函数仅在少数情况下有意义: -- You pass it as a prop to a component wrapped in [`memo`.](/reference/react/memo) You want to skip re-rendering if the value hasn't changed. Memoization lets your component re-render only if dependencies changed. -- The function you're passing is later used as a dependency of some Hook. For example, another function wrapped in `useCallback` depends on it, or you depend on this function from [`useEffect.`](/reference/react/useEffect) +- 将其作为 props 传递给包装在 [`memo`] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。 +- 传递的函数可能作为某些 Hook 的依赖。比如,另一个包裹在 `useCallback` 中的函数依赖于它,或者依赖于 [`useEffect`](/reference/react/useEffect) 中的函数。 -There is no benefit to wrapping a function in `useCallback` in other cases. There is no significant harm to doing that either, so some teams choose to not think about individual cases, and memoize as much as possible. The downside is that code becomes less readable. Also, not all memoization is effective: a single value that's "always new" is enough to break memoization for an entire component. +在其他情况下,将函数包装在 `useCallback` 中没有任何意义。不过即使这样做了,也没有很大的坏处。所以有些团队选择不考虑个案,从而尽可能缓存。不好的地方可能是降低了代码可读性。而且,并不是所有的缓存都是有效的:一个始终是新的值足以破坏整个组件的缓存。 -Note that `useCallback` does not prevent *creating* the function. You're always creating a function (and that's fine!), but React ignores it and gives you back a cached function if nothing changed. +请注意,`useCallback` 不会阻止创建函数。你总是在创建一个函数(这很好!),但是如果没有任何东西改变,React 会忽略它并返回缓存的函数。 -**In practice, you can make a lot of memoization unnecessary by following a few principles:** +**在实践中, 你可以通过遵循一些原则来减少许多不必要的记忆化**: -1. When a component visually wraps other components, let it [accept JSX as children.](/learn/passing-props-to-a-component#passing-jsx-as-children) Then, if the wrapper component updates its own state, React knows that its children don't need to re-render. -1. Prefer local state and don't [lift state up](/learn/sharing-state-between-components) any further than necessary. Don't keep transient state like forms and whether an item is hovered at the top of your tree or in a global state library. -1. Keep your [rendering logic pure.](/learn/keeping-components-pure) If re-rendering a component causes a problem or produces some noticeable visual artifact, it's a bug in your component! Fix the bug instead of adding memoization. -1. Avoid [unnecessary Effects that update state.](/learn/you-might-not-need-an-effect) Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over. -1. Try to [remove unnecessary dependencies from your Effects.](/learn/removing-effect-dependencies) For example, instead of memoization, it's often simpler to move some object or a function inside an Effect or outside the component. +1. 当一个组件在视觉上包装其他组件时,让它 [接受 JSX 作为子元素](/learn/passing-props-to-a-component#passing-jsx-as-children)。随后,如果包装组件更新自己的 state,React 知道它的子组件不需要重新渲染。 +2. 建议使用 state 并且不要 [提升状态](/learn/sharing-state-between-components) 超过必要的程度。不要将表单和项是否悬停等短暂状态保存在树的顶部或全局状态库中。 +3. 保持 [渲染逻辑纯粹](/learn/keeping-components-pure)。如果重新渲染组件会导致问题或产生一些明显的视觉瑕疵,那么这是组件自身的问题!请修复这个错误,而不是添加记忆化。 +4. 避免 [不必要地更新 Effect](/learn/you-might-not-need-an-effect)。React 应用程序中的大多数性能问题都是由 Effect 的更新链引起的,这些更新链不断导致组件重新渲染。 +5. 尝试 [从 Effect 中删除不必要的依赖关系](/learn/removing-effect-dependencies)。例如,将某些对象或函数移动到副作用内部或组件外部通常更简单,而不是使用记忆化。 -If a specific interaction still feels laggy, [use the React Developer Tools profiler](https://legacy.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html) to see which components benefit the most from memoization, and add memoization where needed. These principles make your components easier to debug and understand, so it's good to follow them in any case. In long term, we're researching [doing memoization automatically](https://www.youtube.com/watch?v=lGEMwh32soc) to solve this once and for all. +如果特定的交互仍然感觉滞后,[使用 React 开发者工具](https://legacy.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html) 查看哪些组件在记忆化中受益最大,并在需要时添加记忆化。这些原则使你的组件更易于调试和理解,因此在任何情况下都最好遵循它们。从长远来看,我们正在研究 [自动记忆化](https://www.youtube.com/watch?v=lGEMwh32soc) 以一劳永逸地解决这个问题。 - + -#### Skipping re-rendering with `useCallback` and `memo` {/*skipping-re-rendering-with-usecallback-and-memo*/} +#### 使用 `useCallback` 和 `memo` 跳过函数的重新渲染 {/*skipping-re-rendering-with-usecallback-and-memo*/} -In this example, the `ShippingForm` component is **artificially slowed down** so that you can see what happens when a React component you're rendering is genuinely slow. Try incrementing the counter and toggling the theme. +在这个例子中,`ShippingForm` 组件被人为地减慢了速度,以便你可以看到渲染的 React 组件真正变慢时会发生什么。尝试递增计数器并切换主题。 -Incrementing the counter feels slow because it forces the slowed down `ShippingForm` to re-render. That's expected because the counter has changed, and so you need to reflect the user's new choice on the screen. +递增计数器感觉很慢,因为它会强制变慢 `ShippingForm` 的重新渲染。这是意料之中的,因为计数器已更改,因此你需要在屏幕上反映用户的新选择。 -Next, try toggling the theme. **Thanks to `useCallback` together with [`memo`](/reference/react/memo), it’s fast despite the artificial slowdown!** `ShippingForm` skipped re-rendering because the `handleSubmit` function has not changed. The `handleSubmit` function has not changed because both `productId` and `referrer` (your `useCallback` dependencies) haven't changed since last render. +接下来,尝试更改主题。**将 `useCallback` 和 [`memo`](/reference/react/memo) 结合使用后,尽管人为减缓了速度,但它还是很快**。由于 `useCallback` 依赖 `productId` 与 `referrer` 自上次渲染后始终没有发生改变,因此 `handleSubmit` 也没有改变。由于 `handleSubmit` 没有发生改变,`ShippingForm` 就跳过了重新渲染。 @@ -298,7 +298,7 @@ export default function ProductPage({ productId, referrer, theme }) { } function post(url, data) { - // Imagine this sends a request... + // 想象这发送了一个请求 console.log('POST /' + url); console.log(data); } @@ -313,7 +313,7 @@ const ShippingForm = memo(function ShippingForm({ onSubmit }) { console.log('[ARTIFICIALLY SLOW] Rendering '); let startTime = performance.now(); while (performance.now() - startTime < 500) { - // Do nothing for 500 ms to emulate extremely slow code + // 500 毫秒内不执行任何操作来模拟极慢的代码 } function handleSubmit(e) { @@ -383,11 +383,11 @@ button[type="button"] { -#### Always re-rendering a component {/*always-re-rendering-a-component*/} +#### 始终重新渲染组件 {/*always-re-rendering-a-component*/} -In this example, the `ShippingForm` implementation is also **artificially slowed down** so that you can see what happens when some React component you're rendering is genuinely slow. Try incrementing the counter and toggling the theme. +在本例中,`ShippingForm` 被人为地减慢了速度,这样你可以看到当你渲染的某些 React 组件运行很慢时会发生什么。尝试递增计数器并切换主题。 -Unlike in the previous example, toggling the theme is also slow now! This is because **there is no `useCallback` call in this version,** so `handleSubmit` is always a new function, and the slowed down `ShippingForm` component can't skip re-rendering. +与前面示例不同,现在切换主题也很慢!这是因为 **此处没有调用 `useCallback`**,所以 `handleSubmit` 总是一个新的函数,并且被减速的 `ShippingForm` 组件不能跳过重新渲染。 @@ -437,7 +437,7 @@ export default function ProductPage({ productId, referrer, theme }) { } function post(url, data) { - // Imagine this sends a request... + //想象这发送了一个请求 console.log('POST /' + url); console.log(data); } @@ -452,7 +452,7 @@ const ShippingForm = memo(function ShippingForm({ onSubmit }) { console.log('[ARTIFICIALLY SLOW] Rendering '); let startTime = performance.now(); while (performance.now() - startTime < 500) { - // Do nothing for 500 ms to emulate extremely slow code + // 500 毫秒内不执行任何操作来模拟极慢的代码 } function handleSubmit(e) { @@ -521,7 +521,7 @@ button[type="button"] { -However, here is the same code **with the artificial slowdown removed.** Does the lack of `useCallback` feel noticeable or not? +然而,这里的代码是一致的,只是 **移除了人为减缓的部分**。此时缺少 `useCallback` 是否会感觉明显? @@ -571,7 +571,7 @@ export default function ProductPage({ productId, referrer, theme }) { } function post(url, data) { - // Imagine this sends a request... + // 想象这里发送了一个请求 console.log('POST /' + url); console.log(data); } @@ -650,9 +650,9 @@ button[type="button"] { -Quite often, code without memoization works fine. If your interactions are fast enough, you don't need memoization. +很多时候,没有记忆化的代码运行得也很好。如果你的交互已经足够快了,就不必去使用记忆化。 -Keep in mind that you need to run React in production mode, disable [React Developer Tools](/learn/react-developer-tools), and use devices similar to the ones your app's users have in order to get a realistic sense of what's actually slowing down your app. +注意,如果你需要在生产模式下运行 React,请禁用 [React 开发者工具](/learn/react-developer-tools),并使用与用户类似的设备,以便真实地了解实际减慢应用速度的因素。 @@ -660,11 +660,11 @@ Keep in mind that you need to run React in production mode, disable [React Devel --- -### Updating state from a memoized callback {/*updating-state-from-a-memoized-callback*/} +### 从记忆化回调中更新 state {/*updating-state-from-a-memoized-callback*/} -Sometimes, you might need to update state based on previous state from a memoized callback. +有时,你可能在记忆化回调汇中基于之前的 state 来更新 state。 -This `handleAddTodo` function specifies `todos` as a dependency because it computes the next todos from it: +下面的 `handleAddTodo` 函数将 `todos` 指定为依赖项,因为它会从中计算下一个 todos: ```js {6,7} function TodoList() { @@ -677,7 +677,7 @@ function TodoList() { // ... ``` -You'll usually want memoized functions to have as few dependencies as possible. When you read some state only to calculate the next state, you can remove that dependency by passing an [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) instead: +我们期望记忆化函数具有尽可能少的依赖,当你读取 state 只是为了计算下一个 state 时,你可以通过传递 [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) 以移除该依赖: ```js {6,7} function TodoList() { @@ -686,17 +686,17 @@ function TodoList() { const handleAddTodo = useCallback((text) => { const newTodo = { id: nextId++, text }; setTodos(todos => [...todos, newTodo]); - }, []); // ✅ No need for the todos dependency + }, []); // ✅ 不需要 todos 依赖项 // ... ``` -Here, instead of making `todos` a dependency and reading it inside, you pass an instruction about *how* to update the state (`todos => [...todos, newTodo]`) to React. [Read more about updater functions.](/reference/react/useState#updating-state-based-on-the-previous-state) +在这里,并不是将 `todos` 作为依赖项并在内部读取它,而是传递一个关于 **如何** 更新 state 的指示器 (`todos => [...todos, newTodo]`) 给 React。[阅读更多有关 updater function 的内容](/reference/react/useState#updating-state-based-on-the-previous-state)。 --- -### Preventing an Effect from firing too often {/*preventing-an-effect-from-firing-too-often*/} +### 防止频繁触发 Effect {/*preventing-an-effect-from-firing-too-often*/} -Sometimes, you might want to call a function from inside an [Effect:](/learn/synchronizing-with-effects) +有时,你想要在 [Effect](/learn/synchronizing-with-effects) 内部调用函数: ```js {4-9,12} function ChatRoom({ roomId }) { @@ -716,7 +716,7 @@ function ChatRoom({ roomId }) { // ... ``` -This creates a problem. [Every reactive value must be declared as a dependency of your Effect.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) However, if you declare `createOptions` as a dependency, it will cause your Effect to constantly reconnect to the chat room: +这会产生一个问题,[每一个响应值都必须声明为 Effect 的依赖](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency)。但是如果将 `createOptions` 声明为依赖,它会导致 Effect 不断重新连接到聊天室: ```js {6} @@ -725,11 +725,11 @@ This creates a problem. [Every reactive value must be declared as a dependency o const connection = createConnection(); connection.connect(); return () => connection.disconnect(); - }, [createOptions]); // 🔴 Problem: This dependency changes on every render + }, [createOptions]); // 🔴 问题:这个依赖在每一次渲染中都会发生改变 // ... ``` -To solve this, you can wrap the function you need to call from an Effect into `useCallback`: +为了解决这个问题,需要在 Effect 中将要调用的函数包裹在 `useCallback` 中: ```js {4-9,16} function ChatRoom({ roomId }) { @@ -740,25 +740,25 @@ function ChatRoom({ roomId }) { serverUrl: 'https://localhost:1234', roomId: roomId }; - }, [roomId]); // ✅ Only changes when roomId changes + }, [roomId]); // ✅ 仅当 roomId 更改时更改 useEffect(() => { const options = createOptions(); const connection = createConnection(); connection.connect(); return () => connection.disconnect(); - }, [createOptions]); // ✅ Only changes when createOptions changes + }, [createOptions]); // ✅ 仅当 createOptions 更改时更改 // ... ``` -This ensures that the `createOptions` function is the same between re-renders if the `roomId` is the same. **However, it's even better to remove the need for a function dependency.** Move your function *inside* the Effect: +这将确保如果 `roomId` 相同,`createOptions` 在多次渲染中会是同一个函数。**但是,最好消除对函数依赖项的需求**。将你的函数移入 Effect **内部**: ```js {5-10,16} function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { - function createOptions() { // ✅ No need for useCallback or function dependencies! + function createOptions() { // ✅ 无需使用回调或函数依赖! return { serverUrl: 'https://localhost:1234', roomId: roomId @@ -769,17 +769,17 @@ function ChatRoom({ roomId }) { const connection = createConnection(); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ Only changes when roomId changes + }, [roomId]); // ✅ 仅当 roomId 更改时更改 // ... ``` -Now your code is simpler and doesn't need `useCallback`. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) +现在你的代码变得更简单了并且不需要 `useCallback`。[阅读更多关于移除 Effect 依赖的信息](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect)。 --- -### Optimizing a custom Hook {/*optimizing-a-custom-hook*/} +### 优化自定义 Hook{/*optimizing-a-custom-hook*/} -If you're writing a [custom Hook,](/learn/reusing-logic-with-custom-hooks) it's recommended to wrap any functions that it returns into `useCallback`: +如果你正在编写一个 [自定义 Hook](/learn/reusing-logic-with-custom-hooks),建议将它返回的任何函数包裹在 `useCallback` 中: ```js {4-6,8-10} function useRouter() { @@ -800,17 +800,17 @@ function useRouter() { } ``` -This ensures that the consumers of your Hook can optimize their own code when needed. +这确保了 Hook 的使用者在需要时能够优化自己的代码。 --- -## Troubleshooting {/*troubleshooting*/} +## 疑难解答 {/*troubleshooting*/} -### Every time my component renders, `useCallback` returns a different function {/*every-time-my-component-renders-usecallback-returns-a-different-function*/} +### 我的组件每一次渲染时, `useCallback` 都返回了完全不同的函数 {/*every-time-my-component-renders-usecallback-returns-a-different-function*/} -Make sure you've specified the dependency array as a second argument! +确保你已经将依赖数组指定为第二个参数! -If you forget the dependency array, `useCallback` will return a new function every time: +如果你忘记使用依赖数组,`useCallback` 每一次都将返回一个新的函数: ```js {7} function ProductPage({ productId, referrer }) { @@ -819,11 +819,11 @@ function ProductPage({ productId, referrer }) { referrer, orderDetails, }); - }); // 🔴 Returns a new function every time: no dependency array + }); // 🔴 每一次都返回一个新函数:没有依赖项数组 // ... ``` -This is the corrected version passing the dependency array as a second argument: +这是将依赖项数组作为第二个参数传递的更正版本: ```js {7} function ProductPage({ productId, referrer }) { @@ -832,11 +832,11 @@ function ProductPage({ productId, referrer }) { referrer, orderDetails, }); - }, [productId, referrer]); // ✅ Does not return a new function unnecessarily + }, [productId, referrer]); // ✅ 必要时返回一个新的函数 // ... ``` -If this doesn't help, then the problem is that at least one of your dependencies is different from the previous render. You can debug this problem by manually logging your dependencies to the console: +如果这没有帮助,那么问题是至少有一个依赖项与之前的渲染不同。你可以通过手动将依赖项记录到控制台来调试此问题: ```js {5} const handleSubmit = useCallback((orderDetails) => { @@ -846,28 +846,28 @@ If this doesn't help, then the problem is that at least one of your dependencies console.log([productId, referrer]); ``` -You can then right-click on the arrays from different re-renders in the console and select "Store as a global variable" for both of them. Assuming the first one got saved as `temp1` and the second one got saved as `temp2`, you can then use the browser console to check whether each dependency in both arrays is the same: +然后,你可以在控制台中右键单击来自不同重新渲染的数组,并为它们选择“存储为全局变量”。假设第一个被保存为 `temp1`,第二个被保存为 `temp2`,然后你可以使用浏览器控制台检查两个数组中的每个依赖项是否相同: ```js -Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays? -Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays? -Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ... +Object.is(temp1[0], temp2[0]); // 数组之间的第一个依赖关系是否相同? +Object.is(temp1[1], temp2[1]); // 数组之间的第二个依赖关系是否相同? +Object.is(temp1[2], temp2[2]); // 数组之间的每一个依赖关系是否相同... ``` -When you find which dependency is breaking memoization, either find a way to remove it, or [memoize it as well.](/reference/react/useMemo#memoizing-a-dependency-of-another-hook) +当你发现是某一个依赖性破坏记忆化时,请尝试将其删除,或者 [也对其进行记忆化](/reference/react/useMemo#memoizing-a-dependency-of-another-hook)。 --- -### I need to call `useCallback` for each list item in a loop, but it's not allowed {/*i-need-to-call-usememo-for-each-list-item-in-a-loop-but-its-not-allowed*/} +### 我需要在循环中为每一个列表项调用 `useCallback` 函数,但是这不被允许 {/*i-need-to-call-usememo-for-each-list-item-in-a-loop-but-its-not-allowed*/} -Suppose the `Chart` component is wrapped in [`memo`](/reference/react/memo). You want to skip re-rendering every `Chart` in the list when the `ReportList` component re-renders. However, you can't call `useCallback` in a loop: +假设 `Chart` 组件被包裹在 [`memo`](/reference/react/memo) 中。你希望在 `ReportList` 组件重新渲染时跳过重新渲染列表中的每个 `Chart`。但是,你不能在循环中调用 `useCallback`。 ```js {5-14} function ReportList({ items }) { return (
{items.map(item => { - // 🔴 You can't call useCallback in a loop like this: + // 🔴 你不能在循环中调用 useCallback: const handleClick = useCallback(() => { sendReport(item) }, [item]); @@ -883,7 +883,7 @@ function ReportList({ items }) { } ``` -Instead, extract a component for an individual item, and put `useCallback` there: +相反,为单个项目提取一个组件,然后使用 `useCallback`: ```js {5,12-21} function ReportList({ items }) { @@ -897,7 +897,7 @@ function ReportList({ items }) { } function Report({ item }) { - // ✅ Call useCallback at the top level: + // ✅ 在最顶层调用 useCallback const handleClick = useCallback(() => { sendReport(item) }, [item]); @@ -910,7 +910,7 @@ function Report({ item }) { } ``` -Alternatively, you could remove `useCallback` in the last snippet and instead wrap `Report` itself in [`memo`.](/reference/react/memo) If the `item` prop does not change, `Report` will skip re-rendering, so `Chart` will skip re-rendering too: +或者,你可以删除最后一个代码段中的 `useCallback`,并将 `Report` 本身包装在 [`memo`](/reference/react/memo) 中。如果 `item` props 没有更改,`Report` 将跳过重新渲染,因此 `Chart` 也将跳过重新渲染: ```js {5,6-8,15} function ReportList({ items }) {