diff --git a/src/content/reference/react/useCallback.md b/src/content/reference/react/useCallback.md index 93ce700e4..cc39c1095 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, который позволяет кешировать функции между повторными рендерингами. ```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 вернёт (но не вызовет!) вашу функцию при первом рендере. При последующих рендерах React даст вам ту же функцию, если `dependencies` не изменились. В противном случае он вернёт функцию, переданную при текущем рендере, и сохранит её для возможного повторного использования. 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/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. +* `dependencies`: Список всех реактивных значений, на которые ссылается код `fn`. К реактивным значениям относятся пропсы, состояние и все переменные и функции, объявленные непосредственно в теле компонента. Если ваш линтер [настроен для использования с React](/learn/editor-setup#linting), он проверит, что каждое реактивное значение правильно указано как зависимость. Список зависимостей должен иметь постоянное количество элементов и быть записан примерно так: `[dep1, dep2, dep3]`. React будет сравнивать каждую зависимость с её предыдущим значением, используя алгоритм сравнения [`Object.is`](https://developer.mozilla.org/en-US/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. +Во время последующих рендеров он либо возвращает уже сохранённую функцию `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` -- это хук, поэтому вы можете вызывать его только **на верхнем уровне вашего компонента** или собственных хуков. Вы не можете вызывать его внутри циклов или условий. Если вам это нужно, выделите компонент и перенесите состояние туда. +* React **не выбрасывает кешированную функцию без веской причины.** Например, в режиме разработки React сбрасывает кеш при изменении файла вашего компонента. В разработке и в продакшене кеш сбрасывается, если ваш компонент приостановлен во время начальной загрузки. В будущем React может добавить функции, которые будут использовать сброс кеша -- например, встроенная поддержка виртуализированных списков может потребовать сброса кеша для элементов, которые выходят за пределы области видимости. Это должно соответствовать вашим ожиданиям при использовании `useCallback` для оптимизации производительности. В противном случае, использование [состояния](/reference/react/useState#im-trying-to-set-state-to-a-function-but-it-gets-called-instead) или [рефа](/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`: ```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/en-US/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/ru/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: +Предположим, вы передаёте функцию `handleSubmit` из компонента `ProductPage` в компонент `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` приложение на мгновение зависает, но если убрать `` из вашего 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 рекурсивно отрендерит снова все его дочерние компоненты.** Поэтому, когда `ProductPage` рендерится с другим `theme`, компонент `ShippingForm` *тоже* повторно рендерится. Это нормально для компонентов, которые не требуют больших вычислений при рендере. Но если повторный рендер медленный, можно сказать `ShippingForm` пропустить повторный рендеринг, если его пропсы такие же, как при последнем рендере, обернув его в [`memo`:](/reference/react/memo) ```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`: +**С этим изменением `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... + // Каждый раз, когда тема изменяется, это будет другая функция... 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 никогда не будут одинаковыми, и он будет повторно рендериться каждый раз. */}
); } ``` -**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 () {}` or `() => {}` всегда создаёт _новую_ функцию,** так же как литерал объекта `{}` всегда создаёт новый объект. Обычно это не проблема, но это означает, что пропсы `ShippingForm` никогда не будут одинаковыми, и ваша оптимизация с [`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... + // Сообщите React, чтобы кешировать вашу функцию между повторными рендерами... 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 будет получать те же пропсы и может пропускать повторный рендер */}
); } ``` -**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://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F) (или, другими словами, кешировать) что-то, что вы передаёте вниз по иерархии: ```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`, он не вызывает предоставленную функцию. Вместо этого он кеширует переданную функцию, чтобы `handleSubmit` не изменялся *сам*, если `productId` или `referrer` не изменились. Это позволяет передавать функцию `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) +- Вы передаёте её как проп компоненту, обёрнутому в [`memo`.](/reference/react/memo) Вы хотите пропустить повторный рендер, если значение не изменилось. Мемоизация позволяет вашему компоненту повторно рендериться, только если зависимости изменились. +- Функция, которую вы передаёте, позже используется как зависимость в каком-то хуке. Например, другая функция, обёрнутая в `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) Если обёрточный компонент обновляет своё состояние, React знает, что его дети не нужно повторно рендерить. +1. Предпочитайте локальное состояние и не [поднимайте состояние выше,](/learn/sharing-state-between-components) чем это необходимо. Не держите временное состояние, такое как формы или состояние наведения, на верхнем уровне дерева или в глобальной библиотеке состояния. +1. Держите [логику рендеринга чистой.](/learn/keeping-components-pure) Если повторный рендеринг компонента вызывает проблему или заметные визуальные артефакты, это ошибка в вашем компоненте! Исправьте ошибку вместо добавления мемоизации. +1. Избегайте [ненужных эффектов, которые обновляют состояние.](/learn/you-might-not-need-an-effect) Большинство проблем с производительностью в приложениях React вызвано цепочками обновлений, исходящими от эффектов, которые заставляют ваши компоненты рендериться снова и снова. +1. Попытайтесь [удалить ненужные зависимости из ваших эффектов.](/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 Developer Tools,](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), это происходит быстро, несмотря на искусственное замедление!** `ShippingForm` пропустил повторный рендер, потому что функция `handleSubmit` не изменилась. Функция `handleSubmit` не изменилась, потому что `productId` и `referrer` (зависимости вашего `useCallback`) не изменились с момента последнего рендера. @@ -265,7 +265,7 @@ export default function App() { checked={isDark} onChange={e => setIsDark(e.target.checked)} /> - Dark mode + Тёмный режим
'); + console.log('[ИСКУССТВЕННО ЗАМЕДЛЕНО] Рендеринг '); let startTime = performance.now(); while (performance.now() - startTime < 500) { - // Do nothing for 500 ms to emulate extremely slow code + // Ничего не делаем в течение 500 мс, чтобы имитировать очень медленный код } function handleSubmit(e) { @@ -328,26 +328,26 @@ const ShippingForm = memo(function ShippingForm({ onSubmit }) { return (
-

Note: ShippingForm is artificially slowed down!

+

Примечание: ShippingForm искусственно замедлен!

- +
); }); @@ -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` не может пропустить повторный рендеринг. @@ -405,7 +405,7 @@ export default function App() { checked={isDark} onChange={e => setIsDark(e.target.checked)} /> - Dark mode + Тёмный режим
'); + console.log('[ИСКУССТВЕННО ЗАМЕДЛЕНО] Рендеринг '); let startTime = performance.now(); while (performance.now() - startTime < 500) { - // Do nothing for 500 ms to emulate extremely slow code + // Ничего не делаем в течение 500 мс, чтобы имитировать очень медленный код } function handleSubmit(e) { @@ -467,26 +467,26 @@ const ShippingForm = memo(function ShippingForm({ onSubmit }) { return (
-

Note: ShippingForm is artificially slowed down!

+

Примечание: ShippingForm искусственно замедлен!

- +
); }); @@ -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`? @@ -539,7 +539,7 @@ export default function App() { checked={isDark} onChange={e => setIsDark(e.target.checked)} /> - Dark mode + Тёмный режим
'); + console.log('Рендеринг '); function handleSubmit(e) { e.preventDefault(); @@ -598,24 +598,24 @@ const ShippingForm = memo(function ShippingForm({ onSubmit }) { return (
- +
); }); @@ -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 Developer Tools](/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*/} +### Обновление состояния из мемоизированного колбэка {/*updating-state-from-a-memoized-callback*/} -Sometimes, you might need to update state based on previous state from a memoized callback. +Иногда может потребоваться обновить состояние на основе предыдущего состояния из мемоизированного колбэка. -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: +Обычно вы хотите, чтобы у мемоизированных функций было как можно меньше зависимостей. Когда вы читаете состояние только для вычисления следующего состояния, вы можете удалить эту зависимость, передавая [функцию обновления:](/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` зависимостью и считывать его внутри, вы передаёте React инструкцию о том, *как* обновить состояние (`todos => [...todos, newTodo]`). [Подробнее о функциях обновления.](/reference/react/useState#updating-state-based-on-the-previous-state) --- -### Preventing an Effect from firing too often {/*preventing-an-effect-from-firing-too-often*/} +### Предотвращение слишком частого срабатывания эффекта {/*preventing-an-effect-from-firing-too-often*/} -Sometimes, you might want to call a function from inside an [Effect:](/learn/synchronizing-with-effects) +Иногда может понадобиться вызвать функцию из [эффекта:](/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: +Это создаёт проблему. [Каждое реактивное значение должно быть объявлено как зависимость вашего эффекта.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) Однако, если вы объявите `createOptions` как зависимость, это приведёт к постоянному повторному подключению эффекта к чат-комнате: ```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`: +Чтобы решить эту проблему, вы можете обернуть функцию, которую нужно вызвать из эффекта, в `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: +Это гарантирует, что функция `createOptions` остаётся той же между рендерами, если `roomId` не изменился. **Однако, ещё лучше убрать необходимость в зависимости от функции.** Переместите свою функцию *внутрь* эффекта: ```js {5-10,16} function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { - function createOptions() { // ✅ No need for useCallback or function dependencies! + function createOptions() { // ✅ Нет необходимости в useCallback или зависимостях функции! 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`. [ Узнайте больше об удалении зависимостей эффекта.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) --- -### Optimizing a custom Hook {/*optimizing-a-custom-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`: +Если вы пишете [пользовательский хук,](/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. +Это гарантирует, что потребители вашего хука могут оптимизировать свой код при необходимости. --- -## 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,29 @@ 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: + +Вы можете щёлкнуть правой кнопкой мыши на массивах из разных рендеров в консоли и выбрать "Store as global variable" для обоих. Предположим, первый сохранён как `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). Вы хотите пропустить повторный рендеринг каждого `Chart` в списке, когда компонент `ReportList` рендерится заново. Однако вы не можете вызывать `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 +884,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 +898,7 @@ function ReportList({ items }) { } function Report({ item }) { - // ✅ Call useCallback at the top level: + // ✅ Вызовите useCallback на верхнем уровне: const handleClick = useCallback(() => { sendReport(item) }, [item]); @@ -910,7 +911,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` не изменяется, `Report` пропустит повторный рендеринг, поэтому `Chart` также пропустит повторный рендеринг: ```js {5,6-8,15} function ReportList({ items }) {