From 926b637584f6b07835788287328b5d5b1efa977d Mon Sep 17 00:00:00 2001 From: "translate-react-bot[bot]" <251169733+translate-react-bot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:07:38 +0000 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20translate=20`removing-effect-depend?= =?UTF-8?q?encies.md`=20to=20=D0=A0=D1=83=D1=81=D1=81=D0=BA=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../learn/removing-effect-dependencies.md | 440 +++++++++--------- 1 file changed, 219 insertions(+), 221 deletions(-) diff --git a/src/content/learn/removing-effect-dependencies.md b/src/content/learn/removing-effect-dependencies.md index 9a848862a3..1082285362 100644 --- a/src/content/learn/removing-effect-dependencies.md +++ b/src/content/learn/removing-effect-dependencies.md @@ -1,26 +1,26 @@ --- -title: 'Removing Effect Dependencies' +title: 'Удаление зависимостей эффекта' --- -When you write an Effect, the linter will verify that you've included every reactive value (like props and state) that the Effect reads in the list of your Effect's dependencies. This ensures that your Effect remains synchronized with the latest props and state of your component. Unnecessary dependencies may cause your Effect to run too often, or even create an infinite loop. Follow this guide to review and remove unnecessary dependencies from your Effects. +Когда вы пишете эффект, линтер проверяет, что вы включили каждое реактивное значение (например, пропсы и состояние), которое эффект читает, в список зависимостей вашего эффекта. Это гарантирует, что ваш эффект остается синхронизированным с последними пропсами и состоянием вашего компонента. Ненужные зависимости могут привести к тому, что ваш эффект будет выполняться слишком часто или даже создаст бесконечный цикл. Следуйте этому руководству, чтобы просмотреть и удалить ненужные зависимости из ваших эффектов. -- How to fix infinite Effect dependency loops -- What to do when you want to remove a dependency -- How to read a value from your Effect without "reacting" to it -- How and why to avoid object and function dependencies -- Why suppressing the dependency linter is dangerous, and what to do instead +- Как исправить бесконечные циклы зависимостей эффекта +- Что делать, когда вы хотите удалить зависимость +- Как прочитать значение из вашего эффекта, не "реагируя" на него +- Как и почему избегать зависимостей объектов и функций +- Почему подавление линтера зависимостей опасно и что делать вместо этого -## Dependencies should match the code {/*dependencies-should-match-the-code*/} +## Зависимости должны соответствовать коду {/*dependencies-should-match-the-code*/} -When you write an Effect, you first specify how to [start and stop](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) whatever you want your Effect to be doing: +Когда вы пишете эффект, вы сначала указываете, как [начать и остановить](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) то, что вы хотите, чтобы ваш эффект делал: ```js {5-7} const serverUrl = 'https://localhost:1234'; @@ -34,7 +34,7 @@ function ChatRoom({ roomId }) { } ``` -Then, if you leave the Effect dependencies empty (`[]`), the linter will suggest the correct dependencies: +Затем, если вы оставите зависимости эффекта пустыми (`[]`), линтер предложит правильные зависимости: @@ -49,7 +49,7 @@ function ChatRoom({ roomId }) { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); - }, []); // <-- Fix the mistake here! + }, []); // <-- Исправьте ошибку здесь! return

Welcome to the {roomId} room!

; } @@ -96,7 +96,7 @@ button { margin-left: 10px; }
-Fill them in according to what the linter says: +Заполните их в соответствии с тем, что говорит линтер: ```js {6} function ChatRoom({ roomId }) { @@ -104,12 +104,12 @@ function ChatRoom({ roomId }) { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ All dependencies declared + }, [roomId]); // ✅ Все зависимости объявлены // ... } ``` -[Effects "react" to reactive values.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Since `roomId` is a reactive value (it can change due to a re-render), the linter verifies that you've specified it as a dependency. If `roomId` receives a different value, React will re-synchronize your Effect. This ensures that the chat stays connected to the selected room and "reacts" to the dropdown: +[Эффекты "реагируют" на реактивные значения.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Поскольку `roomId` является реактивным значением (оно может измениться из-за повторного рендеринга), линтер проверяет, что вы указали его как зависимость. Если `roomId` получит другое значение, React повторно синхронизирует ваш эффект. Это гарантирует, что чат останется подключенным к выбранной комнате и будет "реагировать" на выпадающий список: @@ -171,24 +171,24 @@ button { margin-left: 10px; } -### To remove a dependency, prove that it's not a dependency {/*to-remove-a-dependency-prove-that-its-not-a-dependency*/} +### Чтобы удалить зависимость, докажите, что она не является зависимостью {/*to-remove-a-dependency-prove-that-its-not-a-dependency*/} -Notice that you can't "choose" the dependencies of your Effect. Every reactive value used by your Effect's code must be declared in your dependency list. The dependency list is determined by the surrounding code: +Обратите внимание, что вы не можете "выбирать" зависимости вашего эффекта. Каждое реактивное значение, используемое кодом вашего эффекта, должно быть объявлено в вашем списке зависимостей. Список зависимостей определяется окружающим кодом: ```js [[2, 3, "roomId"], [2, 5, "roomId"], [2, 8, "roomId"]] const serverUrl = 'https://localhost:1234'; -function ChatRoom({ roomId }) { // This is a reactive value +function ChatRoom({ roomId }) { // Это реактивное значение useEffect(() => { - const connection = createConnection(serverUrl, roomId); // This Effect reads that reactive value + const connection = createConnection(serverUrl, roomId); // Этот эффект читает это реактивное значение connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ So you must specify that reactive value as a dependency of your Effect + }, [roomId]); // ✅ Поэтому вы должны указать это реактивное значение как зависимость вашего эффекта // ... } ``` -[Reactive values](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) include props and all variables and functions declared directly inside of your component. Since `roomId` is a reactive value, you can't remove it from the dependency list. The linter wouldn't allow it: +[Реактивные значения](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) включают пропсы и все переменные и функции, объявленные непосредственно внутри вашего компонента. Поскольку `roomId` является реактивным значением, вы не можете удалить его из списка зависимостей. Линтер не позволит этого: ```js {8} const serverUrl = 'https://localhost:1234'; @@ -203,25 +203,25 @@ function ChatRoom({ roomId }) { } ``` -And the linter would be right! Since `roomId` may change over time, this would introduce a bug in your code. +И линтер будет прав! Поскольку `roomId` может меняться со временем, это приведет к ошибке в вашем коде. -**To remove a dependency, "prove" to the linter that it *doesn't need* to be a dependency.** For example, you can move `roomId` out of your component to prove that it's not reactive and won't change on re-renders: +**Чтобы удалить зависимость, "докажите" линтеру, что она *не нуждается* в том, чтобы быть зависимостью.** Например, вы можете переместить `roomId` за пределы вашего компонента, чтобы доказать, что он не реактивный и не изменится при повторных рендерингах: ```js {2,9} const serverUrl = 'https://localhost:1234'; -const roomId = 'music'; // Not a reactive value anymore +const roomId = 'music'; // Больше не реактивное значение function ChatRoom() { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); - }, []); // ✅ All dependencies declared + }, []); // ✅ Все зависимости объявлены // ... } ``` -Now that `roomId` is not a reactive value (and can't change on a re-render), it doesn't need to be a dependency: +Теперь, поскольку `roomId` не является реактивным значением (и не может измениться при повторном рендеринге), оно не нуждается в том, чтобы быть зависимостью: @@ -263,43 +263,43 @@ button { margin-left: 10px; } -This is why you could now specify an [empty (`[]`) dependency list.](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) Your Effect *really doesn't* depend on any reactive value anymore, so it *really doesn't* need to re-run when any of the component's props or state change. +Вот почему вы теперь можете указать [пустой (`[]`) список зависимостей.](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) Ваш эффект *действительно не* зависит ни от какого реактивного значения, поэтому он *действительно не* нуждается в повторном запуске при изменении каких-либо пропсов или состояния компонента. -### To change the dependencies, change the code {/*to-change-the-dependencies-change-the-code*/} +### Чтобы изменить зависимости, измените код {/*to-change-the-dependencies-change-the-code*/} -You might have noticed a pattern in your workflow: +Вы могли заметить закономерность в своем рабочем процессе: -1. First, you **change the code** of your Effect or how your reactive values are declared. -2. Then, you follow the linter and adjust the dependencies to **match the code you have changed.** -3. If you're not happy with the list of dependencies, you **go back to the first step** (and change the code again). +1. Сначала вы **изменяете код** вашего эффекта или способ объявления ваших реактивных значений. +2. Затем вы следуете за линтером и корректируете зависимости, чтобы они **соответствовали измененному вами коду.** +3. Если вас не устраивает список зависимостей, вы **возвращаетесь к первому шагу** (и снова изменяете код). -The last part is important. **If you want to change the dependencies, change the surrounding code first.** You can think of the dependency list as [a list of all the reactive values used by your Effect's code.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) You don't *choose* what to put on that list. The list *describes* your code. To change the dependency list, change the code. +Последняя часть важна. **Если вы хотите изменить зависимости, сначала измените окружающий код.** Вы можете рассматривать список зависимостей как [список всех реактивных значений, используемых кодом вашего эффекта.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) Вы не *выбираете*, что поместить в этот список. Список *описывает* ваш код. Чтобы изменить список зависимостей, измените код. -This might feel like solving an equation. You might start with a goal (for example, to remove a dependency), and you need to "find" the code matching that goal. Not everyone finds solving equations fun, and the same thing could be said about writing Effects! Luckily, there is a list of common recipes that you can try below. +Это может показаться решением уравнения. Вы можете начать с цели (например, удалить зависимость), и вам нужно "найти" код, соответствующий этой цели. Не всем нравится решать уравнения, и то же самое можно сказать о написании эффектов! К счастью, ниже приведен список распространенных рецептов, которые вы можете попробовать. -If you have an existing codebase, you might have some Effects that suppress the linter like this: +Если у вас есть существующая кодовая база, у вас могут быть некоторые эффекты, которые подавляют линтер следующим образом: ```js {3-4} useEffect(() => { // ... - // 🔴 Avoid suppressing the linter like this: + // 🔴 Избегайте подавления линтера таким образом: // eslint-ignore-next-line react-hooks/exhaustive-deps }, []); ``` -**When dependencies don't match the code, there is a very high risk of introducing bugs.** By suppressing the linter, you "lie" to React about the values your Effect depends on. +**Когда зависимости не соответствуют коду, существует очень высокий риск внесения ошибок.** Подавляя линтер, вы "лжете" React о значениях, от которых зависит ваш эффект. -Instead, use the techniques below. +Вместо этого используйте приведенные ниже методы. -#### Why is suppressing the dependency linter so dangerous? {/*why-is-suppressing-the-dependency-linter-so-dangerous*/} +#### Почему подавление линтера зависимостей так опасно? {/*why-is-suppressing-the-dependency-linter-so-dangerous*/} -Suppressing the linter leads to very unintuitive bugs that are hard to find and fix. Here's one example: +Подавление линтера приводит к очень неинтуитивным ошибкам, которые трудно найти и исправить. Вот один пример: @@ -348,31 +348,31 @@ button { margin: 10px; } -Let's say that you wanted to run the Effect "only on mount". You've read that [empty (`[]`) dependencies](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) do that, so you've decided to ignore the linter, and forcefully specified `[]` as the dependencies. +Допустим, вы хотели запустить эффект "только при монтировании". Вы прочитали, что [пустые (`[]`) зависимости](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) делают это, поэтому вы решили проигнорировать линтер и принудительно указали `[]` в качестве зависимостей. -This counter was supposed to increment every second by the amount configurable with the two buttons. However, since you "lied" to React that this Effect doesn't depend on anything, React forever keeps using the `onTick` function from the initial render. [During that render,](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` was `0` and `increment` was `1`. This is why `onTick` from that render always calls `setCount(0 + 1)` every second, and you always see `1`. Bugs like this are harder to fix when they're spread across multiple components. +Этот счетчик должен был увеличиваться каждую секунду на величину, настраиваемую двумя кнопками. Однако, поскольку вы "солгал" React, что этот эффект ни от чего не зависит, React навсегда сохраняет функцию `onTick` из начального рендеринга. [Во время этого рендеринга](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` был `0`, а `increment` был `1`. Вот почему `onTick` из этого рендеринга всегда вызывает `setCount(0 + 1)` каждую секунду, и вы всегда видите `1`. Ошибки, подобные этой, труднее исправить, когда они распределены по нескольким компонентам. -There's always a better solution than ignoring the linter! To fix this code, you need to add `onTick` to the dependency list. (To ensure the interval is only setup once, [make `onTick` an Effect Event.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)) +Всегда есть лучшее решение, чем игнорировать линтер! Чтобы исправить этот код, вам нужно добавить `onTick` в список зависимостей. (Чтобы гарантировать, что интервал устанавливается только один раз, [сделайте `onTick` событием эффекта.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)) -**We recommend treating the dependency lint error as a compilation error. If you don't suppress it, you will never see bugs like this.** The rest of this page documents the alternatives for this and other cases. +**Мы рекомендуем относиться к ошибке линтера зависимостей как к ошибке компиляции. Если вы не подавите ее, вы никогда не увидите подобных ошибок.** Остальная часть этой страницы документирует альтернативы для этого и других случаев. -## Removing unnecessary dependencies {/*removing-unnecessary-dependencies*/} +## Удаление ненужных зависимостей {/*removing-unnecessary-dependencies*/} -Every time you adjust the Effect's dependencies to reflect the code, look at the dependency list. Does it make sense for the Effect to re-run when any of these dependencies change? Sometimes, the answer is "no": +Каждый раз, когда вы корректируете зависимости эффекта, чтобы они отражали код, взгляните на список зависимостей. Имеет ли смысл, чтобы эффект перезапускался при изменении любой из этих зависимостей? Иногда ответ — «нет»: -* You might want to re-execute *different parts* of your Effect under different conditions. -* You might want to only read the *latest value* of some dependency instead of "reacting" to its changes. -* A dependency may change too often *unintentionally* because it's an object or a function. +* Вы можете захотеть перезапустить *различные части* вашего эффекта при разных условиях. +* Вы можете захотеть только прочитать *последнее значение* некоторой зависимости, вместо того чтобы «реагировать» на её изменения. +* Зависимость может слишком часто меняться *непреднамеренно*, потому что это объект или функция. -To find the right solution, you'll need to answer a few questions about your Effect. Let's walk through them. +Чтобы найти правильное решение, вам нужно будет ответить на несколько вопросов о вашем эффекте. Давайте пройдемся по ним. -### Should this code move to an event handler? {/*should-this-code-move-to-an-event-handler*/} +### Следует ли переместить этот код в обработчик событий? {/*should-this-code-move-to-an-event-handler*/} -The first thing you should think about is whether this code should be an Effect at all. +Первое, о чем следует подумать, — это должен ли этот код вообще быть эффектом. -Imagine a form. On submit, you set the `submitted` state variable to `true`. You need to send a POST request and show a notification. You've put this logic inside an Effect that "reacts" to `submitted` being `true`: +Представьте форму. При отправке вы устанавливаете переменную состояния `submitted` в `true`. Вам нужно отправить POST-запрос и показать уведомление. Вы поместили эту логику внутрь эффекта, который «реагирует» на `submitted`, равное `true`: ```js {6-8} function Form() { @@ -380,7 +380,7 @@ function Form() { useEffect(() => { if (submitted) { - // 🔴 Avoid: Event-specific logic inside an Effect + // 🔴 Избегайте: Логика, специфичная для событий, внутри эффекта post('/api/register'); showNotification('Successfully registered!'); } @@ -394,7 +394,7 @@ function Form() { } ``` -Later, you want to style the notification message according to the current theme, so you read the current theme. Since `theme` is declared in the component body, it is a reactive value, so you add it as a dependency: +Позже вы хотите стилизовать сообщение уведомления в соответствии с текущей темой, поэтому вы читаете текущую тему. Поскольку `theme` объявлена в теле компонента, это реактивное значение, поэтому вы добавляете её в зависимости: ```js {3,9,11} function Form() { @@ -403,11 +403,11 @@ function Form() { useEffect(() => { if (submitted) { - // 🔴 Avoid: Event-specific logic inside an Effect + // 🔴 Избегайте: Логика, специфичная для событий, внутри эффекта post('/api/register'); showNotification('Successfully registered!', theme); } - }, [submitted, theme]); // ✅ All dependencies declared + }, [submitted, theme]); // ✅ Все зависимости объявлены function handleSubmit() { setSubmitted(true); @@ -417,16 +417,16 @@ function Form() { } ``` -By doing this, you've introduced a bug. Imagine you submit the form first and then switch between Dark and Light themes. The `theme` will change, the Effect will re-run, and so it will display the same notification again! +Сделав это, вы внесли ошибку. Представьте, что вы сначала отправляете форму, а затем переключаетесь между темной и светлой темами. `theme` изменится, эффект перезапустится, и поэтому он снова отобразит то же уведомление! -**The problem here is that this shouldn't be an Effect in the first place.** You want to send this POST request and show the notification in response to *submitting the form,* which is a particular interaction. To run some code in response to particular interaction, put that logic directly into the corresponding event handler: +**Проблема здесь в том, что это вообще не должно быть эффектом.** Вы хотите отправить этот POST-запрос и показать уведомление в ответ на *отправку формы*, что является конкретным взаимодействием. Чтобы выполнить какой-либо код в ответ на конкретное взаимодействие, поместите эту логику непосредственно в соответствующий обработчик событий: ```js {6-7} function Form() { const theme = useContext(ThemeContext); function handleSubmit() { - // ✅ Good: Event-specific logic is called from event handlers + // ✅ Хорошо: Логика, специфичная для событий, вызывается из обработчиков событий post('/api/register'); showNotification('Successfully registered!', theme); } @@ -435,13 +435,13 @@ function Form() { } ``` -Now that the code is in an event handler, it's not reactive--so it will only run when the user submits the form. Read more about [choosing between event handlers and Effects](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) and [how to delete unnecessary Effects.](/learn/you-might-not-need-an-effect) +Теперь, когда код находится в обработчике событий, он не является реактивным — он будет выполняться только тогда, когда пользователь отправляет форму. Читайте больше о [выборе между обработчиками событий и эффектами](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) и о [том, как удалить ненужные эффекты.](/learn/you-might-not-need-an-effect) -### Is your Effect doing several unrelated things? {/*is-your-effect-doing-several-unrelated-things*/} +### Ваш эффект выполняет несколько несвязанных действий? {/*is-your-effect-doing-several-unrelated-things*/} -The next question you should ask yourself is whether your Effect is doing several unrelated things. +Следующий вопрос, который вы должны себе задать, заключается в том, выполняет ли ваш эффект несколько несвязанных действий. -Imagine you're creating a shipping form where the user needs to choose their city and area. You fetch the list of `cities` from the server according to the selected `country` to show them in a dropdown: +Представьте, что вы создаете форму доставки, где пользователю нужно выбрать город и район. Вы получаете список `cities` с сервера в соответствии с выбранной `country`, чтобы показать их в выпадающем списке: ```js function ShippingForm({ country }) { @@ -460,14 +460,14 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country]); // ✅ All dependencies declared + }, [country]); // ✅ Все зависимости объявлены // ... ``` -This is a good example of [fetching data in an Effect.](/learn/you-might-not-need-an-effect#fetching-data) You are synchronizing the `cities` state with the network according to the `country` prop. You can't do this in an event handler because you need to fetch as soon as `ShippingForm` is displayed and whenever the `country` changes (no matter which interaction causes it). +Это хороший пример [получения данных в эффекте.](/learn/you-might-not-need-an-effect#fetching-data) Вы синхронизируете состояние `cities` с сетью в соответствии с пропом `country`. Вы не можете сделать это в обработчике событий, потому что вам нужно получать данные, как только `ShippingForm` отобразится, и всякий раз, когда `country` изменится (независимо от того, какое взаимодействие это вызвало). -Now let's say you're adding a second select box for city areas, which should fetch the `areas` for the currently selected `city`. You might start by adding a second `fetch` call for the list of areas inside the same Effect: +Теперь давайте предположим, что вы добавляете второй выпадающий список для районов города, который должен получать `areas` для выбранного в данный момент `city`. Вы можете начать с добавления второго вызова `fetch` для списка районов внутри того же эффекта: ```js {15-24,28} function ShippingForm({ country }) { @@ -484,7 +484,7 @@ function ShippingForm({ country }) { setCities(json); } }); - // 🔴 Avoid: A single Effect synchronizes two independent processes + // 🔴 Избегайте: Один эффект синхронизирует два независимых процесса if (city) { fetch(`/api/areas?city=${city}`) .then(response => response.json()) @@ -497,19 +497,19 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country, city]); // ✅ All dependencies declared + }, [country, city]); // ✅ Все зависимости объявлены // ... ``` -However, since the Effect now uses the `city` state variable, you've had to add `city` to the list of dependencies. That, in turn, introduced a problem: when the user selects a different city, the Effect will re-run and call `fetchCities(country)`. As a result, you will be unnecessarily refetching the list of cities many times. +Однако, поскольку эффект теперь использует переменную состояния `city`, вам пришлось добавить `city` в список зависимостей. Это, в свою очередь, привело к проблеме: когда пользователь выбирает другой город, эффект перезапустится и вызовет `fetchCities(country)`. В результате вы будете ненужно перезапрашивать список городов много раз. -**The problem with this code is that you're synchronizing two different unrelated things:** +**Проблема этого кода в том, что вы синхронизируете две разные несвязанные вещи:** -1. You want to synchronize the `cities` state to the network based on the `country` prop. -1. You want to synchronize the `areas` state to the network based on the `city` state. +1. Вы хотите синхронизировать состояние `cities` с сетью на основе пропа `country`. +1. Вы хотите синхронизировать состояние `areas` с сетью на основе состояния `city`. -Split the logic into two Effects, each of which reacts to the prop that it needs to synchronize with: +Разделите логику на два эффекта, каждый из которых реагирует на проп, с которым ему нужно синхронизироваться: ```js {19-33} function ShippingForm({ country }) { @@ -526,7 +526,7 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country]); // ✅ All dependencies declared + }, [country]); // ✅ Все зависимости объявлены const [city, setCity] = useState(null); const [areas, setAreas] = useState(null); @@ -544,18 +544,18 @@ function ShippingForm({ country }) { ignore = true; }; } - }, [city]); // ✅ All dependencies declared + }, [city]); // ✅ Все зависимости объявлены // ... ``` -Now the first Effect only re-runs if the `country` changes, while the second Effect re-runs when the `city` changes. You've separated them by purpose: two different things are synchronized by two separate Effects. Two separate Effects have two separate dependency lists, so they won't trigger each other unintentionally. +Теперь первый эффект перезапускается только в том случае, если `country` изменится, а второй эффект перезапускается, когда `city` изменится. Вы разделили их по назначению: две разные вещи синхронизируются двумя отдельными эффектами. Два отдельных эффекта имеют два отдельных списка зависимостей, поэтому они не будут непреднамеренно запускать друг друга. -The final code is longer than the original, but splitting these Effects is still correct. [Each Effect should represent an independent synchronization process.](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) In this example, deleting one Effect doesn't break the other Effect's logic. This means they *synchronize different things,* and it's good to split them up. If you're concerned about duplication, you can improve this code by [extracting repetitive logic into a custom Hook.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks) +Итоговый код длиннее исходного, но разделение этих эффектов по-прежнему является правильным. [Каждый эффект должен представлять собой отдельный процесс синхронизации.](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) В этом примере удаление одного эффекта не нарушает логику другого. Это означает, что они *синхронизируют разные вещи*, и их хорошо разделять. Если вас беспокоит дублирование, вы можете улучшить этот код, [выделив повторяющуюся логику в пользовательский хук.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks) -### Are you reading some state to calculate the next state? {/*are-you-reading-some-state-to-calculate-the-next-state*/} +### Вы читаете некоторое состояние для вычисления следующего состояния? {/*are-you-reading-some-state-to-calculate-the-next-state*/} -This Effect updates the `messages` state variable with a newly created array every time a new message arrives: +Этот эффект обновляет переменную состояния `messages` новым созданным массивом каждый раз, когда приходит новое сообщение: ```js {2,6-8} function ChatRoom({ roomId }) { @@ -569,7 +569,7 @@ function ChatRoom({ roomId }) { // ... ``` -It uses the `messages` variable to [create a new array](/learn/updating-arrays-in-state) starting with all the existing messages and adds the new message at the end. However, since `messages` is a reactive value read by an Effect, it must be a dependency: +Он использует переменную `messages` для [создания нового массива](/learn/updating-arrays-in-state), начиная со всех существующих сообщений и добавляя новое сообщение в конец. Однако, поскольку `messages` является реактивным значением, прочитанным эффектом, оно должно быть зависимостью: ```js {7,10} function ChatRoom({ roomId }) { @@ -581,15 +581,15 @@ function ChatRoom({ roomId }) { setMessages([...messages, receivedMessage]); }); return () => connection.disconnect(); - }, [roomId, messages]); // ✅ All dependencies declared + }, [roomId, messages]); // ✅ Все зависимости объявлены // ... ``` -And making `messages` a dependency introduces a problem. +И добавление `messages` в зависимости создает проблему. -Every time you receive a message, `setMessages()` causes the component to re-render with a new `messages` array that includes the received message. However, since this Effect now depends on `messages`, this will *also* re-synchronize the Effect. So every new message will make the chat re-connect. The user would not like that! +Каждый раз, когда вы получаете сообщение, `setMessages()` вызывает повторный рендеринг компонента с новым массивом `messages`, который включает полученное сообщение. Однако, поскольку этот эффект теперь зависит от `messages`, это *также* приведет к повторной синхронизации эффекта. Таким образом, каждое новое сообщение приведет к повторному подключению чата. Пользователю это не понравится! -To fix the issue, don't read `messages` inside the Effect. Instead, pass an [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) to `setMessages`: +Чтобы устранить проблему, не читайте `messages` внутри эффекта. Вместо этого передайте [функцию обновления](/reference/react/useState#updating-state-based-on-the-previous-state) в `setMessages`: ```js {7,10} function ChatRoom({ roomId }) { @@ -601,21 +601,21 @@ function ChatRoom({ roomId }) { setMessages(msgs => [...msgs, receivedMessage]); }); return () => connection.disconnect(); - }, [roomId]); // ✅ All dependencies declared + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -**Notice how your Effect does not read the `messages` variable at all now.** You only need to pass an updater function like `msgs => [...msgs, receivedMessage]`. React [puts your updater function in a queue](/learn/queueing-a-series-of-state-updates) and will provide the `msgs` argument to it during the next render. This is why the Effect itself doesn't need to depend on `messages` anymore. As a result of this fix, receiving a chat message will no longer make the chat re-connect. +**Обратите внимание, что ваш эффект теперь вообще не читает переменную `messages`.** Вам нужно только передать функцию обновления, такую как `msgs => [...msgs, receivedMessage]`. React [помещает вашу функцию обновления в очередь](/learn/queueing-a-series-of-state-updates) и предоставит ей аргумент `msgs` во время следующего рендеринга. Вот почему сам эффект больше не должен зависеть от `messages`. В результате этого исправления получение сообщения чата больше не приведет к повторному подключению чата. -### Do you want to read a value without "reacting" to its changes? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/} +### Хотите прочитать значение, не «реагируя» на его изменения? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/} -This section describes an **experimental API that has not yet been released** in a stable version of React. +Этот раздел описывает **экспериментальный API, который ещё не был выпущен** в стабильной версии React. -Suppose that you want to play a sound when the user receives a new message unless `isMuted` is `true`: +Предположим, вы хотите воспроизвести звук, когда пользователь получает новое сообщение, если только `isMuted` не равно `true`: ```js {3,10-12} function ChatRoom({ roomId }) { @@ -634,7 +634,7 @@ function ChatRoom({ roomId }) { // ... ``` -Since your Effect now uses `isMuted` in its code, you have to add it to the dependencies: +Поскольку ваш Effect теперь использует `isMuted` в своём коде, вы должны добавить его в зависимости: ```js {10,15} function ChatRoom({ roomId }) { @@ -651,13 +651,13 @@ function ChatRoom({ roomId }) { } }); return () => connection.disconnect(); - }, [roomId, isMuted]); // ✅ All dependencies declared + }, [roomId, isMuted]); // ✅ Все зависимости объявлены // ... ``` -The problem is that every time `isMuted` changes (for example, when the user presses the "Muted" toggle), the Effect will re-synchronize, and reconnect to the chat. This is not the desired user experience! (In this example, even disabling the linter would not work--if you do that, `isMuted` would get "stuck" with its old value.) +Проблема в том, что каждый раз, когда `isMuted` изменяется (например, когда пользователь нажимает переключатель "Без звука"), Effect будет повторно синхронизироваться и переподключаться к чату. Это нежелательный пользовательский опыт! (В этом примере даже отключение линтера не помогло бы — если вы это сделаете, `isMuted` "застрянет" со своим старым значением.) -To solve this problem, you need to extract the logic that shouldn't be reactive out of the Effect. You don't want this Effect to "react" to the changes in `isMuted`. [Move this non-reactive piece of logic into an Effect Event:](/learn/separating-events-from-effects#declaring-an-effect-event) +Чтобы решить эту проблему, вам нужно извлечь логику, которая не должна быть реактивной, из Effect. Вы не хотите, чтобы этот Effect «реагировал» на изменения `isMuted`. [Переместите эту нереактивную часть логики в Event Effect:](/learn/separating-events-from-effects#declaring-an-effect-event) ```js {1,7-12,18,21} import { useState, useEffect, useEffectEvent } from 'react'; @@ -680,15 +680,15 @@ function ChatRoom({ roomId }) { onMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId]); // ✅ All dependencies declared + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -Effect Events let you split an Effect into reactive parts (which should "react" to reactive values like `roomId` and their changes) and non-reactive parts (which only read their latest values, like `onMessage` reads `isMuted`). **Now that you read `isMuted` inside an Effect Event, it doesn't need to be a dependency of your Effect.** As a result, the chat won't re-connect when you toggle the "Muted" setting on and off, solving the original issue! +Event Effects позволяют разделить Effect на реактивные части (которые должны «реагировать» на реактивные значения, такие как `roomId`, и их изменения) и нереактивные части (которые только считывают свои последние значения, например, `onMessage` считывает `isMuted`). **Теперь, когда вы считываете `isMuted` внутри Event Effect, оно больше не должно быть зависимостью вашего Effect.** В результате чат не будет переподключаться при включении и выключении настройки "Без звука", решая исходную проблему! -#### Wrapping an event handler from the props {/*wrapping-an-event-handler-from-the-props*/} +#### Обертывание обработчика событий из пропсов {/*wrapping-an-event-handler-from-the-props*/} -You might run into a similar problem when your component receives an event handler as a prop: +Вы можете столкнуться с похожей проблемой, когда ваш компонент получает обработчик событий в виде пропса: ```js {1,8,11} function ChatRoom({ roomId, onReceiveMessage }) { @@ -701,11 +701,11 @@ function ChatRoom({ roomId, onReceiveMessage }) { onReceiveMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId, onReceiveMessage]); // ✅ All dependencies declared + }, [roomId, onReceiveMessage]); // ✅ Все зависимости объявлены // ... ``` -Suppose that the parent component passes a *different* `onReceiveMessage` function on every render: +Предположим, родительский компонент передаёт *разную* функцию `onReceiveMessage` при каждом рендере: ```js {3-5} ``` -Since `onReceiveMessage` is a dependency, it would cause the Effect to re-synchronize after every parent re-render. This would make it re-connect to the chat. To solve this, wrap the call in an Effect Event: +Поскольку `onReceiveMessage` является зависимостью, это вызовет повторную синхронизацию Effect после каждого рендера родителя. Это приведёт к повторному подключению к чату. Чтобы решить эту проблему, оберните вызов в Event Effect: ```js {4-6,12,15} function ChatRoom({ roomId, onReceiveMessage }) { @@ -733,17 +733,17 @@ function ChatRoom({ roomId, onReceiveMessage }) { onMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId]); // ✅ All dependencies declared + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -Effect Events aren't reactive, so you don't need to specify them as dependencies. As a result, the chat will no longer re-connect even if the parent component passes a function that's different on every re-render. +Event Effects не являются реактивными, поэтому вам не нужно указывать их как зависимости. В результате чат больше не будет переподключаться, даже если родительский компонент передаёт функцию, которая отличается при каждом рендере. -#### Separating reactive and non-reactive code {/*separating-reactive-and-non-reactive-code*/} +#### Разделение реактивного и нереактивного кода {/*separating-reactive-and-non-reactive-code*/} -In this example, you want to log a visit every time `roomId` changes. You want to include the current `notificationCount` with every log, but you *don't* want a change to `notificationCount` to trigger a log event. +В этом примере вы хотите регистрировать посещение каждый раз, когда изменяется `roomId`. Вы хотите включить текущее `notificationCount` в каждый лог, но вы *не* хотите, чтобы изменение `notificationCount` вызывало событие лога. -The solution is again to split out the non-reactive code into an Effect Event: +Решение снова заключается в разделении нереактивного кода на Event Effect: ```js {2-4,7} function Chat({ roomId, notificationCount }) { @@ -753,16 +753,16 @@ function Chat({ roomId, notificationCount }) { useEffect(() => { onVisit(roomId); - }, [roomId]); // ✅ All dependencies declared + }, [roomId]); // ✅ Все зависимости объявлены // ... } ``` -You want your logic to be reactive with regards to `roomId`, so you read `roomId` inside of your Effect. However, you don't want a change to `notificationCount` to log an extra visit, so you read `notificationCount` inside of the Effect Event. [Learn more about reading the latest props and state from Effects using Effect Events.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) +Вы хотите, чтобы ваша логика была реактивной по отношению к `roomId`, поэтому вы считываете `roomId` внутри вашего Effect. Однако вы не хотите, чтобы изменение `notificationCount` регистрировало дополнительное посещение, поэтому вы считываете `notificationCount` внутри Event Effect. [Узнайте больше о чтении последних пропсов и состояния из Effects с помощью Event Effects.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) -### Does some reactive value change unintentionally? {/*does-some-reactive-value-change-unintentionally*/} +### Значение реактивного значения меняется непреднамеренно? {/*does-some-reactive-value-change-unintentionally*/} -Sometimes, you *do* want your Effect to "react" to a certain value, but that value changes more often than you'd like--and might not reflect any actual change from the user's perspective. For example, let's say that you create an `options` object in the body of your component, and then read that object from inside of your Effect: +Иногда вы *хотите*, чтобы ваш Effect «реагировал» на определённое значение, но это значение меняется чаще, чем вам хотелось бы, и, возможно, не отражает реальных изменений с точки зрения пользователя. Например, предположим, вы создаёте объект `options` в теле вашего компонента, а затем читаете этот объект внутри вашего Effect: ```js {3-6,9} function ChatRoom({ roomId }) { @@ -778,7 +778,7 @@ function ChatRoom({ roomId }) { // ... ``` -This object is declared in the component body, so it's a [reactive value.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) When you read a reactive value like this inside an Effect, you declare it as a dependency. This ensures your Effect "reacts" to its changes: +Этот объект объявлен в теле компонента, поэтому он является [реактивным значением.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Когда вы читаете реактивное значение таким образом внутри Effect, вы объявляете его как зависимость. Это гарантирует, что ваш Effect будет «реагировать» на его изменения: ```js {3,6} // ... @@ -786,11 +786,11 @@ This object is declared in the component body, so it's a [reactive value.](/lear const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [options]); // ✅ All dependencies declared + }, [options]); // ✅ Все зависимости объявлены // ... ``` -It is important to declare it as a dependency! This ensures, for example, that if the `roomId` changes, your Effect will re-connect to the chat with the new `options`. However, there is also a problem with the code above. To see it, try typing into the input in the sandbox below, and watch what happens in the console: +Важно объявить его как зависимость! Это гарантирует, например, что если `roomId` изменится, ваш Effect переподключится к чату с новыми `options`. Однако в коде выше есть и проблема. Чтобы увидеть её, попробуйте ввести текст в поле ввода в песочнице ниже и посмотрите, что происходит в консоли: @@ -803,7 +803,7 @@ const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); - // Temporarily disable the linter to demonstrate the problem + // Временно отключим линтер, чтобы продемонстрировать проблему // eslint-disable-next-line react-hooks/exhaustive-deps const options = { serverUrl: serverUrl, @@ -867,30 +867,30 @@ button { margin-left: 10px; } -In the sandbox above, the input only updates the `message` state variable. From the user's perspective, this should not affect the chat connection. However, every time you update the `message`, your component re-renders. When your component re-renders, the code inside of it runs again from scratch. +В песочнице выше поле ввода только обновляет переменную состояния `message`. С точки зрения пользователя, это не должно влиять на подключение к чату. Однако каждый раз, когда вы обновляете `message`, ваш компонент перерисовывается. Когда ваш компонент перерисовывается, код внутри него выполняется заново с самого начала. -A new `options` object is created from scratch on every re-render of the `ChatRoom` component. React sees that the `options` object is a *different object* from the `options` object created during the last render. This is why it re-synchronizes your Effect (which depends on `options`), and the chat re-connects as you type. +Новый объект `options` создаётся с нуля при каждой перерисовке компонента `ChatRoom`. React видит, что объект `options` — это *другой объект*, отличный от объекта `options`, созданного во время предыдущего рендеринга. Именно поэтому он повторно синхронизирует ваш Effect (который зависит от `options`), и чат переподключается по мере ввода текста. -**This problem only affects objects and functions. In JavaScript, each newly created object and function is considered distinct from all the others. It doesn't matter that the contents inside of them may be the same!** +**Эта проблема затрагивает только объекты и функции. В JavaScript каждый вновь созданный объект и функция считаются отличными от всех остальных. Неважно, что содержимое внутри них может быть одинаковым!** ```js {7-8} -// During the first render +// Во время первого рендеринга const options1 = { serverUrl: 'https://localhost:1234', roomId: 'music' }; -// During the next render +// Во время следующего рендеринга const options2 = { serverUrl: 'https://localhost:1234', roomId: 'music' }; -// These are two different objects! +// Это два разных объекта! console.log(Object.is(options1, options2)); // false ``` -**Object and function dependencies can make your Effect re-synchronize more often than you need.** +**Зависимости от объектов и функций могут заставлять ваш Effect синхронизироваться чаще, чем вам нужно.** -This is why, whenever possible, you should try to avoid objects and functions as your Effect's dependencies. Instead, try moving them outside the component, inside the Effect, or extracting primitive values out of them. +Вот почему, когда это возможно, вам следует избегать объектов и функций в качестве зависимостей вашего Effect. Вместо этого попробуйте переместить их за пределы компонента, внутрь Effect или извлечь примитивные значения из них. -#### Move static objects and functions outside your component {/*move-static-objects-and-functions-outside-your-component*/} +#### Переместите статические объекты и функции за пределы вашего компонента {/*move-static-objects-and-functions-outside-your-component*/} -If the object does not depend on any props and state, you can move that object outside your component: +Если объект не зависит от каких-либо пропсов и состояния, вы можете переместить этот объект за пределы вашего компонента: ```js {1-4,13} const options = { @@ -905,13 +905,13 @@ function ChatRoom() { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, []); // ✅ All dependencies declared + }, []); // ✅ Все зависимости объявлены // ... ``` -This way, you *prove* to the linter that it's not reactive. It can't change as a result of a re-render, so it doesn't need to be a dependency. Now re-rendering `ChatRoom` won't cause your Effect to re-synchronize. +Таким образом, вы *доказываете* линтеру, что это не реактивное значение. Оно не может измениться в результате перерисовки, поэтому ему не нужно быть зависимостью. Теперь перерисовка `ChatRoom` не вызовет повторную синхронизацию вашего Effect. -This works for functions too: +Это работает и для функций: ```js {1-6,12} function createOptions() { @@ -929,15 +929,15 @@ function ChatRoom() { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, []); // ✅ All dependencies declared + }, []); // ✅ Все зависимости объявлены // ... ``` -Since `createOptions` is declared outside your component, it's not a reactive value. This is why it doesn't need to be specified in your Effect's dependencies, and why it won't ever cause your Effect to re-synchronize. +Поскольку `createOptions` объявлена за пределами вашего компонента, она не является реактивным значением. Вот почему её не нужно указывать в зависимостях вашего Effect, и почему она никогда не вызовет повторную синхронизацию вашего Effect. -#### Move dynamic objects and functions inside your Effect {/*move-dynamic-objects-and-functions-inside-your-effect*/} +#### Переместите динамические объекты и функции внутрь вашего Effect {/*move-dynamic-objects-and-functions-inside-your-effect*/} -If your object depends on some reactive value that may change as a result of a re-render, like a `roomId` prop, you can't pull it *outside* your component. You can, however, move its creation *inside* of your Effect's code: +Если ваш объект зависит от какого-либо реактивного значения, которое может измениться в результате перерисовки, например, пропса `roomId`, вы не можете вынести его *за пределы* компонента. Однако вы можете переместить его создание *внутрь* кода вашего Effect: ```js {7-10,11,14} const serverUrl = 'https://localhost:1234'; @@ -953,24 +953,24 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ All dependencies declared + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -Now that `options` is declared inside of your Effect, it is no longer a dependency of your Effect. Instead, the only reactive value used by your Effect is `roomId`. Since `roomId` is not an object or function, you can be sure that it won't be *unintentionally* different. In JavaScript, numbers and strings are compared by their content: +Теперь, когда `options` объявлен внутри вашего Effect, он больше не является зависимостью вашего Effect. Вместо этого единственным реактивным значением, используемым вашим Effect, является `roomId`. Поскольку `roomId` не является объектом или функцией, вы можете быть уверены, что он не будет *непреднамеренно* отличаться. В JavaScript числа и строки сравниваются по их содержимому: ```js {7-8} -// During the first render +// Во время первого рендеринга const roomId1 = 'music'; -// During the next render +// Во время следующего рендеринга const roomId2 = 'music'; -// These two strings are the same! +// Эти две строки одинаковы! console.log(Object.is(roomId1, roomId2)); // true ``` -Thanks to this fix, the chat no longer re-connects if you edit the input: +Благодаря этому исправлению чат больше не переподключается, если вы редактируете поле ввода: @@ -1044,9 +1044,9 @@ button { margin-left: 10px; } -However, it *does* re-connect when you change the `roomId` dropdown, as you would expect. +Однако он *переподключается*, когда вы меняете выпадающий список `roomId`, как и ожидалось. -This works for functions, too: +Это работает и для функций: ```js {7-12,14} const serverUrl = 'https://localhost:1234'; @@ -1066,15 +1066,15 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ All dependencies declared + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -You can write your own functions to group pieces of logic inside your Effect. As long as you also declare them *inside* your Effect, they're not reactive values, and so they don't need to be dependencies of your Effect. +Вы можете писать свои собственные функции для группировки частей логики внутри вашего Effect. Пока вы также объявляете их *внутри* вашего Effect, они не являются реактивными значениями, и поэтому им не нужно быть зависимостями вашего Effect. -#### Read primitive values from objects {/*read-primitive-values-from-objects*/} +#### Читайте примитивные значения из объектов {/*read-primitive-values-from-objects*/} -Sometimes, you may receive an object from props: +Иногда вы можете получать объект из пропсов: ```js {1,5,8} function ChatRoom({ options }) { @@ -1084,11 +1084,11 @@ function ChatRoom({ options }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [options]); // ✅ All dependencies declared + }, [options]); // ✅ Все зависимости объявлены // ... ``` -The risk here is that the parent component will create the object during rendering: +Риск здесь в том, что родительский компонент создаст объект во время рендеринга: ```js {3-6} ``` -This would cause your Effect to re-connect every time the parent component re-renders. To fix this, read information from the object *outside* the Effect, and avoid having object and function dependencies: +Это приведёт к тому, что ваш Effect будет переподключаться при каждой перерисовке родительского компонента. Чтобы исправить это, читайте информацию из объекта *вне* Effect и избегайте зависимостей от объектов и функций: ```js {4,7-8,12} function ChatRoom({ options }) { @@ -1114,15 +1114,15 @@ function ChatRoom({ options }) { }); connection.connect(); return () => connection.disconnect(); - }, [roomId, serverUrl]); // ✅ All dependencies declared + }, [roomId, serverUrl]); // ✅ Все зависимости объявлены // ... ``` -The logic gets a little repetitive (you read some values from an object outside an Effect, and then create an object with the same values inside the Effect). But it makes it very explicit what information your Effect *actually* depends on. If an object is re-created unintentionally by the parent component, the chat would not re-connect. However, if `options.roomId` or `options.serverUrl` really are different, the chat would re-connect. +Логика становится немного повторяющейся (вы читаете некоторые значения из объекта вне Effect, а затем создаёте объект с теми же значениями внутри Effect). Но это делает очень явным, от какой информации ваш Effect *фактически* зависит. Если объект непреднамеренно пересоздаётся родительским компонентом, чат не будет переподключаться. Однако, если `options.roomId` или `options.serverUrl` действительно отличаются, чат переподключится. -#### Calculate primitive values from functions {/*calculate-primitive-values-from-functions*/} +#### Вычисляйте примитивные значения из функций {/*calculate-primitive-values-from-functions*/} -The same approach can work for functions. For example, suppose the parent component passes a function: +Тот же подход может работать и для функций. Например, предположим, родительский компонент передаёт функцию: ```js {3-8} ``` -To avoid making it a dependency (and causing it to re-connect on re-renders), call it outside the Effect. This gives you the `roomId` and `serverUrl` values that aren't objects, and that you can read from inside your Effect: +Чтобы избежать её включения в зависимости (и, как следствие, повторных переподключений при перерисовках), вызовите её вне Effect. Это даст вам значения `roomId` и `serverUrl`, которые не являются объектами и которые вы можете читать внутри вашего Effect: ```js {1,4} function ChatRoom({ getOptions }) { @@ -1150,36 +1150,36 @@ function ChatRoom({ getOptions }) { }); connection.connect(); return () => connection.disconnect(); - }, [roomId, serverUrl]); // ✅ All dependencies declared + }, [roomId, serverUrl]); // ✅ Все зависимости объявлены // ... ``` -This only works for [pure](/learn/keeping-components-pure) functions because they are safe to call during rendering. If your function is an event handler, but you don't want its changes to re-synchronize your Effect, [wrap it into an Effect Event instead.](#do-you-want-to-read-a-value-without-reacting-to-its-changes) +Это работает только для [чистых](/learn/keeping-components-pure) функций, поскольку их безопасно вызывать во время рендеринга. Если ваша функция является обработчиком событий, но вы не хотите, чтобы её изменения вызывали повторную синхронизацию вашего Effect, [оберните её в Effect Event вместо этого.](#do-you-want-to-read-a-value-without-reacting-to-its-changes) -- Dependencies should always match the code. -- When you're not happy with your dependencies, what you need to edit is the code. -- Suppressing the linter leads to very confusing bugs, and you should always avoid it. -- To remove a dependency, you need to "prove" to the linter that it's not necessary. -- If some code should run in response to a specific interaction, move that code to an event handler. -- If different parts of your Effect should re-run for different reasons, split it into several Effects. -- If you want to update some state based on the previous state, pass an updater function. -- If you want to read the latest value without "reacting" it, extract an Effect Event from your Effect. -- In JavaScript, objects and functions are considered different if they were created at different times. -- Try to avoid object and function dependencies. Move them outside the component or inside the Effect. +- Зависимости всегда должны соответствовать коду. +- Когда вы недовольны своими зависимостями, вам нужно редактировать код. +- Подавление линтера приводит к очень запутанным ошибкам, и вам всегда следует его избегать. +- Чтобы удалить зависимость, вам нужно «доказать» линтеру, что она не нужна. +- Если какой-то код должен выполняться в ответ на определённое взаимодействие, переместите этот код в обработчик событий. +- Если разные части вашего Effect должны перезапускаться по разным причинам, разделите его на несколько Effect. +- Если вы хотите обновить состояние на основе предыдущего состояния, передайте функцию-обновлятор. +- Если вы хотите прочитать последнее значение, не «реагируя» на него, извлеките Effect Event из вашего Effect. +- В JavaScript объекты и функции считаются разными, если они были созданы в разное время. +- Старайтесь избегать зависимостей от объектов и функций. Переместите их за пределы компонента или внутрь Effect. -#### Fix a resetting interval {/*fix-a-resetting-interval*/} +#### Исправление сбрасываемого интервала {/*fix-a-resetting-interval*/} -This Effect sets up an interval that ticks every second. You've noticed something strange happening: it seems like the interval gets destroyed and re-created every time it ticks. Fix the code so that the interval doesn't get constantly re-created. +Этот эффект настраивает интервал, который срабатывает каждую секунду. Вы заметили странное поведение: кажется, что интервал уничтожается и воссоздается каждый раз, когда срабатывает. Исправьте код так, чтобы интервал не воссоздавался постоянно. -It seems like this Effect's code depends on `count`. Is there some way to not need this dependency? There should be a way to update the `count` state based on its previous value without adding a dependency on that value. +Кажется, что код этого эффекта зависит от `count`. Есть ли способ обойтись без этой зависимости? Должен быть способ обновить состояние `count` на основе его предыдущего значения, не добавляя зависимость от этого значения. @@ -1211,9 +1211,9 @@ export default function Timer() { -You want to update the `count` state to be `count + 1` from inside the Effect. However, this makes your Effect depend on `count`, which changes with every tick, and that's why your interval gets re-created on every tick. +Вы хотите обновить состояние `count` до значения `count + 1` изнутри эффекта. Однако это делает ваш эффект зависимым от `count`, который изменяется при каждом тике, и именно поэтому ваш интервал воссоздается при каждом тике. -To solve this, use the [updater function](/reference/react/useState#updating-state-based-on-the-previous-state) and write `setCount(c => c + 1)` instead of `setCount(count + 1)`: +Чтобы решить эту проблему, используйте [функцию обновления](/reference/react/useState#updating-state-based-on-the-previous-state) и напишите `setCount(c => c + 1)` вместо `setCount(count + 1)`: @@ -1241,19 +1241,19 @@ export default function Timer() { -Instead of reading `count` inside the Effect, you pass a `c => c + 1` instruction ("increment this number!") to React. React will apply it on the next render. And since you don't need to read the value of `count` inside your Effect anymore, you can keep your Effect's dependencies empty (`[]`). This prevents your Effect from re-creating the interval on every tick. +Вместо того чтобы считывать `count` внутри эффекта, вы передаете инструкцию `c => c + 1` («увеличь это число!») в React. React применит ее при следующем рендеринге. И поскольку вам больше не нужно считывать значение `count` внутри вашего эффекта, вы можете оставить зависимости эффекта пустыми (`[]`). Это предотвращает повторное создание интервала эффектом при каждом тике. -#### Fix a retriggering animation {/*fix-a-retriggering-animation*/} +#### Исправление повторно запускаемой анимации {/*fix-a-retriggering-animation*/} -In this example, when you press "Show", a welcome message fades in. The animation takes a second. When you press "Remove", the welcome message immediately disappears. The logic for the fade-in animation is implemented in the `animation.js` file as plain JavaScript [animation loop.](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) You don't need to change that logic. You can treat it as a third-party library. Your Effect creates an instance of `FadeInAnimation` for the DOM node, and then calls `start(duration)` or `stop()` to control the animation. The `duration` is controlled by a slider. Adjust the slider and see how the animation changes. +В этом примере при нажатии кнопки «Показать» появляется всплывающее сообщение. Анимация занимает одну секунду. При нажатии кнопки «Удалить» сообщение немедленно исчезает. Логика анимации появления реализована в файле `animation.js` как обычный JavaScript [цикл анимации.](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) Вам не нужно менять эту логику. Вы можете рассматривать ее как стороннюю библиотеку. Ваш эффект создает экземпляр `FadeInAnimation` для DOM-узла, а затем вызывает `start(duration)` или `stop()`, чтобы управлять анимацией. Длительность контролируется ползунком. Настройте ползунок и посмотрите, как меняется анимация. -This code already works, but there is something you want to change. Currently, when you move the slider that controls the `duration` state variable, it retriggers the animation. Change the behavior so that the Effect does not "react" to the `duration` variable. When you press "Show", the Effect should use the current `duration` on the slider. However, moving the slider itself should not by itself retrigger the animation. +Этот код уже работает, но есть кое-что, что вы хотите изменить. В настоящее время, когда вы перемещаете ползунок, который контролирует переменную состояния `duration`, это повторно запускает анимацию. Измените поведение так, чтобы эффект не «реагировал» на переменную `duration`. Когда вы нажимаете «Показать», эффект должен использовать текущее значение `duration` на ползунке. Однако само перемещение ползунка не должно само по себе повторно запускать анимацию. -Is there a line of code inside the Effect that should not be reactive? How can you move non-reactive code out of the Effect? +Есть ли в коде эффекта строка, которая не должна быть реактивной? Как вы можете вынести нереактивный код из эффекта? @@ -1382,7 +1382,7 @@ html, body { min-height: 300px; } -Your Effect needs to read the latest value of `duration`, but you don't want it to "react" to changes in `duration`. You use `duration` to start the animation, but starting animation isn't reactive. Extract the non-reactive line of code into an Effect Event, and call that function from your Effect. +Вашему эффекту необходимо считывать последнее значение `duration`, но вы не хотите, чтобы он «реагировал» на изменения `duration`. Вы используете `duration` для запуска анимации, но запуск анимации не является реактивным. Вынесите нереактивную строку кода в Event Effect и вызовите эту функцию из вашего эффекта. @@ -1505,19 +1505,19 @@ html, body { min-height: 300px; } -Effect Events like `onAppear` are not reactive, so you can read `duration` inside without retriggering the animation. +Event Effects, такие как `onAppear`, не являются реактивными, поэтому вы можете считывать `duration` внутри них, не вызывая повторного запуска анимации. -#### Fix a reconnecting chat {/*fix-a-reconnecting-chat*/} +#### Исправьте переподключение чата {/*fix-a-reconnecting-chat*/} -In this example, every time you press "Toggle theme", the chat re-connects. Why does this happen? Fix the mistake so that the chat re-connects only when you edit the Server URL or choose a different chat room. +В этом примере каждый раз при нажатии кнопки «Toggle theme» чат переподключается. Почему это происходит? Исправьте ошибку так, чтобы чат переподключался только при изменении URL сервера или выборе другой комнаты чата. -Treat `chat.js` as an external third-party library: you can consult it to check its API, but don't edit it. +Считайте `chat.js` внешней сторонней библиотекой: вы можете обращаться к ней для проверки её API, но не редактируйте её. -There's more than one way to fix this, but ultimately you want to avoid having an object as your dependency. +Существует несколько способов исправить это, но в конечном итоге вам следует избегать использования объекта в качестве зависимости. @@ -1611,9 +1611,9 @@ label, button { display: block; margin-bottom: 5px; } -Your Effect is re-running because it depends on the `options` object. Objects can be re-created unintentionally, you should try to avoid them as dependencies of your Effects whenever possible. +Ваш эффект перезапускается, потому что он зависит от объекта `options`. Объекты могут быть созданы непреднамеренно, поэтому старайтесь избегать их в качестве зависимостей ваших эффектов, когда это возможно. -The least invasive fix is to read `roomId` and `serverUrl` right outside the Effect, and then make the Effect depend on those primitive values (which can't change unintentionally). Inside the Effect, create an object and pass it to `createConnection`: +Наименее инвазивным исправлением является чтение `roomId` и `serverUrl` непосредственно перед эффектом и затем установка эффекта в зависимость от этих примитивных значений (которые не могут быть изменены непреднамеренно). Внутри эффекта создайте объект и передайте его в `createConnection`: @@ -1707,7 +1707,7 @@ label, button { display: block; margin-bottom: 5px; } -It would be even better to replace the object `options` prop with the more specific `roomId` and `serverUrl` props: +Было бы еще лучше заменить объектный проп `options` на более конкретные пропы `roomId` и `serverUrl`: @@ -1798,25 +1798,25 @@ label, button { display: block; margin-bottom: 5px; } -Sticking to primitive props where possible makes it easier to optimize your components later. +Использование примитивных пропсов, где это возможно, упрощает последующую оптимизацию компонентов. -#### Fix a reconnecting chat, again {/*fix-a-reconnecting-chat-again*/} +#### Исправляем переподключающийся чат {/*fix-a-reconnecting-chat-again*/} -This example connects to the chat either with or without encryption. Toggle the checkbox and notice the different messages in the console when the encryption is on and off. Try changing the room. Then, try toggling the theme. When you're connected to a chat room, you will receive new messages every few seconds. Verify that their color matches the theme you've picked. +Этот пример подключается к чату с шифрованием или без него. Переключите флажок и обратите внимание на разные сообщения в консоли при включенном и выключенном шифровании. Попробуйте сменить комнату. Затем попробуйте переключить тему. Когда вы подключены к комнате чата, вы будете получать новые сообщения каждые несколько секунд. Убедитесь, что их цвет соответствует выбранной вами теме. -In this example, the chat re-connects every time you try to change the theme. Fix this. After the fix, changing the theme should not re-connect the chat, but toggling encryption settings or changing the room should re-connect. +В этом примере чат переподключается каждый раз, когда вы пытаетесь сменить тему. Исправьте это. После исправления смена темы не должна переподключать чат, но переключение настроек шифрования или смена комнаты должны вызывать переподключение. -Don't change any code in `chat.js`. Other than that, you can change any code as long as it results in the same behavior. For example, you may find it helpful to change which props are being passed down. +Не меняйте код в `chat.js`. Кроме этого, вы можете изменять любой код, если это приведет к такому же поведению. Например, вам может быть полезно изменить передаваемые пропсы. -You're passing down two functions: `onMessage` and `createConnection`. Both of them are created from scratch every time `App` re-renders. They are considered to be new values every time, which is why they re-trigger your Effect. +Вы передаете две функции: `onMessage` и `createConnection`. Обе они создаются с нуля при каждом перерендере `App`. Они считаются новыми значениями каждый раз, поэтому повторно запускают ваш эффект. -One of these functions is an event handler. Do you know some way to call an event handler an Effect without "reacting" to the new values of the event handler function? That would come in handy! +Одна из этих функций является обработчиком событий. Вы знаете какой-нибудь способ вызвать обработчик событий в эффекте без "реакции" на новые значения функции обработчика? Это было бы полезно! -Another of these functions only exists to pass some state to an imported API method. Is this function really necessary? What is the essential information that's being passed down? You might need to move some imports from `App.js` to `ChatRoom.js`. +Одна из этих функций существует только для передачи некоторого состояния импортированному методу API. Действительно ли эта функция необходима? Какая существенная информация передается? Возможно, вам потребуется переместить некоторые импорты из `App.js` в `ChatRoom.js`. @@ -2031,11 +2031,11 @@ label, button { display: block; margin-bottom: 5px; } -There's more than one correct way to solve this, but here is one possible solution. +Существует более одного правильного способа решить эту проблему, но вот одно из возможных решений. -In the original example, toggling the theme caused different `onMessage` and `createConnection` functions to be created and passed down. Since the Effect depended on these functions, the chat would re-connect every time you toggle the theme. +В исходном примере переключение темы приводило к созданию и передаче разных функций `onMessage` и `createConnection`. Поскольку эффект зависел от этих функций, чат переподключался каждый раз при переключении темы. -To fix the problem with `onMessage`, you needed to wrap it into an Effect Event: +Чтобы исправить проблему с `onMessage`, вам нужно было обернуть ее в событие эффекта (Effect Event): ```js {1,2,6} export default function ChatRoom({ roomId, createConnection, onMessage }) { @@ -2047,9 +2047,9 @@ export default function ChatRoom({ roomId, createConnection, onMessage }) { // ... ``` -Unlike the `onMessage` prop, the `onReceiveMessage` Effect Event is not reactive. This is why it doesn't need to be a dependency of your Effect. As a result, changes to `onMessage` won't cause the chat to re-connect. +В отличие от пропса `onMessage`, событие эффекта `onReceiveMessage` не является реактивным. Поэтому оно не должно быть зависимостью вашего эффекта. В результате изменения `onMessage` не вызовут переподключение чата. -You can't do the same with `createConnection` because it *should* be reactive. You *want* the Effect to re-trigger if the user switches between an encrypted and an unencryption connection, or if the user switches the current room. However, because `createConnection` is a function, you can't check whether the information it reads has *actually* changed or not. To solve this, instead of passing `createConnection` down from the `App` component, pass the raw `roomId` and `isEncrypted` values: +Вы не можете сделать то же самое с `createConnection`, потому что оно *должно* быть реактивным. Вы *хотите*, чтобы эффект повторно запускался, если пользователь переключается между зашифрованным и незашифрованным соединением, или если пользователь меняет текущую комнату. Однако, поскольку `createConnection` является функцией, вы не можете проверить, изменилась ли информация, которую она читает, *фактически*. Чтобы решить эту проблему, вместо передачи `createConnection` из компонента `App`, передайте необработанные значения `roomId` и `isEncrypted`: ```js {2-3} ``` -Now you can move the `createConnection` function *inside* the Effect instead of passing it down from the `App`: +Теперь вы можете переместить функцию `createConnection` *внутрь* эффекта вместо передачи ее из `App`: ```js {1-4,6,10-20} import { @@ -2087,19 +2087,19 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // ... ``` -After these two changes, your Effect no longer depends on any function values: +После этих двух изменений ваш эффект больше не зависит от значений функций: ```js {1,8,10,21} -export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reactive values - const onReceiveMessage = useEffectEvent(onMessage); // Not reactive +export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Реактивные значения + const onReceiveMessage = useEffectEvent(onMessage); // Не реактивное useEffect(() => { function createConnection() { const options = { serverUrl: 'https://localhost:1234', - roomId: roomId // Reading a reactive value + roomId: roomId // Чтение реактивного значения }; - if (isEncrypted) { // Reading a reactive value + if (isEncrypted) { // Чтение реактивного значения return createEncryptedConnection(options); } else { return createUnencryptedConnection(options); @@ -2110,10 +2110,10 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reacti connection.on('message', (msg) => onReceiveMessage(msg)); connection.connect(); return () => connection.disconnect(); - }, [roomId, isEncrypted]); // ✅ All dependencies declared + }, [roomId, isEncrypted]); // ✅ Все зависимости объявлены ``` -As a result, the chat re-connects only when something meaningful (`roomId` or `isEncrypted`) changes: +В результате чат переподключается только тогда, когда изменяется что-то значимое (`roomId` или `isEncrypted`): @@ -2219,22 +2219,20 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { return

Welcome to the {roomId} room!

; } -``` - -```js src/chat.js +```javascript src/chat.js export function createEncryptedConnection({ serverUrl, roomId }) { - // A real implementation would actually connect to the server + // Реальная реализация будет подключаться к серверу if (typeof serverUrl !== 'string') { - throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + throw Error('Ожидался serverUrl типа string. Получено: ' + serverUrl); } if (typeof roomId !== 'string') { - throw Error('Expected roomId to be a string. Received: ' + roomId); + throw Error('Ожидался roomId типа string. Получено: ' + roomId); } let intervalId; let messageCallback; return { connect() { - console.log('✅ 🔐 Connecting to "' + roomId + '" room... (encrypted)'); + console.log('✅ 🔐 Подключение к комнате "' + roomId + '"... (зашифровано)'); clearInterval(intervalId); intervalId = setInterval(() => { if (messageCallback) { @@ -2249,14 +2247,14 @@ export function createEncryptedConnection({ serverUrl, roomId }) { disconnect() { clearInterval(intervalId); messageCallback = null; - console.log('❌ 🔐 Disconnected from "' + roomId + '" room (encrypted)'); + console.log('❌ 🔐 Отключено от комнаты "' + roomId + '" (зашифровано)'); }, on(event, callback) { if (messageCallback) { - throw Error('Cannot add the handler twice.'); + throw Error('Нельзя добавить обработчик дважды.'); } if (event !== 'message') { - throw Error('Only "message" event is supported.'); + throw Error('Поддерживается только событие "message".'); } messageCallback = callback; }, @@ -2264,18 +2262,18 @@ export function createEncryptedConnection({ serverUrl, roomId }) { } export function createUnencryptedConnection({ serverUrl, roomId }) { - // A real implementation would actually connect to the server + // Реальная реализация будет подключаться к серверу if (typeof serverUrl !== 'string') { - throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); + throw Error('Ожидался serverUrl типа string. Получено: ' + serverUrl); } if (typeof roomId !== 'string') { - throw Error('Expected roomId to be a string. Received: ' + roomId); + throw Error('Ожидался roomId типа string. Получено: ' + roomId); } let intervalId; let messageCallback; return { connect() { - console.log('✅ Connecting to "' + roomId + '" room (unencrypted)...'); + console.log('✅ Подключение к комнате "' + roomId + '" (незашифровано)...'); clearInterval(intervalId); intervalId = setInterval(() => { if (messageCallback) { @@ -2290,14 +2288,14 @@ export function createUnencryptedConnection({ serverUrl, roomId }) { disconnect() { clearInterval(intervalId); messageCallback = null; - console.log('❌ Disconnected from "' + roomId + '" room (unencrypted)'); + console.log('❌ Отключено от комнаты "' + roomId + '" (незашифровано)'); }, on(event, callback) { if (messageCallback) { - throw Error('Cannot add the handler twice.'); + throw Error('Нельзя добавить обработчик дважды.'); } if (event !== 'message') { - throw Error('Only "message" event is supported.'); + throw Error('Поддерживается только событие "message".'); } messageCallback = callback; }, @@ -2305,7 +2303,7 @@ export function createUnencryptedConnection({ serverUrl, roomId }) { } ``` -```js src/notifications.js +```javascript src/notifications.js import Toastify from 'toastify-js'; import 'toastify-js/src/toastify.css'; @@ -2331,4 +2329,4 @@ label, button { display: block; margin-bottom: 5px; }
-
+ \ No newline at end of file From 62f96c454d4364ddef0810c35d8aabbd0a4ae26c Mon Sep 17 00:00:00 2001 From: "translate-react-bot[bot]" <251169733+translate-react-bot[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 15:36:05 +0000 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20translate=20`removing-effect-depend?= =?UTF-8?q?encies.md`=20to=20=D0=A0=D1=83=D1=81=D1=81=D0=BA=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../learn/removing-effect-dependencies.md | 375 +++++++++--------- 1 file changed, 193 insertions(+), 182 deletions(-) diff --git a/src/content/learn/removing-effect-dependencies.md b/src/content/learn/removing-effect-dependencies.md index 1082285362..d962275f42 100644 --- a/src/content/learn/removing-effect-dependencies.md +++ b/src/content/learn/removing-effect-dependencies.md @@ -1,26 +1,26 @@ --- -title: 'Удаление зависимостей эффекта' +title: 'Удаление зависимостей эффектов' --- -Когда вы пишете эффект, линтер проверяет, что вы включили каждое реактивное значение (например, пропсы и состояние), которое эффект читает, в список зависимостей вашего эффекта. Это гарантирует, что ваш эффект остается синхронизированным с последними пропсами и состоянием вашего компонента. Ненужные зависимости могут привести к тому, что ваш эффект будет выполняться слишком часто или даже создаст бесконечный цикл. Следуйте этому руководству, чтобы просмотреть и удалить ненужные зависимости из ваших эффектов. +Когда вы пишете Effect, линтер проверит, что вы включили каждое реактивное значение (например, пропсы и состояние), которое Effect считывает, в список зависимостей вашего Effect. Это гарантирует, что ваш Effect остается синхронизированным с последними пропсами и состоянием вашего компонента. Необязательные зависимости могут привести к тому, что ваш Effect будет запускаться слишком часто или даже создаст бесконечный цикл. Следуйте этому руководству, чтобы просмотреть и удалить ненужные зависимости из ваших Effects. -- Как исправить бесконечные циклы зависимостей эффекта +- Как исправить бесконечные циклы зависимостей Effect - Что делать, когда вы хотите удалить зависимость -- Как прочитать значение из вашего эффекта, не "реагируя" на него -- Как и почему избегать зависимостей объектов и функций +- Как прочитать значение из вашего Effect, не «реагируя» на него +- Как и почему следует избегать зависимостей от объектов и функций - Почему подавление линтера зависимостей опасно и что делать вместо этого ## Зависимости должны соответствовать коду {/*dependencies-should-match-the-code*/} -Когда вы пишете эффект, вы сначала указываете, как [начать и остановить](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) то, что вы хотите, чтобы ваш эффект делал: +Когда вы пишете Effect, вы сначала указываете, как [запустить и остановить](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) все, что вы хотите, чтобы ваш Effect делал: ```js {5-7} const serverUrl = 'https://localhost:1234'; @@ -34,7 +34,7 @@ function ChatRoom({ roomId }) { } ``` -Затем, если вы оставите зависимости эффекта пустыми (`[]`), линтер предложит правильные зависимости: +Затем, если вы оставите зависимости Effect пустыми (`[]`), линтер предложит правильные зависимости: @@ -109,7 +109,7 @@ function ChatRoom({ roomId }) { } ``` -[Эффекты "реагируют" на реактивные значения.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Поскольку `roomId` является реактивным значением (оно может измениться из-за повторного рендеринга), линтер проверяет, что вы указали его как зависимость. Если `roomId` получит другое значение, React повторно синхронизирует ваш эффект. Это гарантирует, что чат останется подключенным к выбранной комнате и будет "реагировать" на выпадающий список: +[Effects «реагируют» на реактивные значения.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Поскольку `roomId` является реактивным значением (оно может измениться из-за повторного рендеринга), линтер проверяет, что вы указали его в качестве зависимости. Если `roomId` получает другое значение, React повторно синхронизирует ваш Effect. Это гарантирует, что чат остается подключенным к выбранной комнате и «реагирует» на раскрывающийся список: @@ -173,22 +173,22 @@ button { margin-left: 10px; } ### Чтобы удалить зависимость, докажите, что она не является зависимостью {/*to-remove-a-dependency-prove-that-its-not-a-dependency*/} -Обратите внимание, что вы не можете "выбирать" зависимости вашего эффекта. Каждое реактивное значение, используемое кодом вашего эффекта, должно быть объявлено в вашем списке зависимостей. Список зависимостей определяется окружающим кодом: +Обратите внимание, что вы не можете «выбрать» зависимости вашего Effect. Каждое реактивное значение, используемое кодом вашего Effect, должно быть объявлено в списке ваших зависимостей. Список зависимостей определяется окружающим кодом: ```js [[2, 3, "roomId"], [2, 5, "roomId"], [2, 8, "roomId"]] const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { // Это реактивное значение useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Этот эффект читает это реактивное значение + const connection = createConnection(serverUrl, roomId); // Этот Effect считывает это реактивное значение connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ Поэтому вы должны указать это реактивное значение как зависимость вашего эффекта + }, [roomId]); // ✅ Поэтому вы должны указать это реактивное значение в качестве зависимости вашего Effect // ... } ``` -[Реактивные значения](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) включают пропсы и все переменные и функции, объявленные непосредственно внутри вашего компонента. Поскольку `roomId` является реактивным значением, вы не можете удалить его из списка зависимостей. Линтер не позволит этого: +[Реактивные значения](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) включают пропсы и все переменные и функции, объявленные непосредственно внутри вашего компонента. Поскольку `roomId` является реактивным значением, вы не можете удалить его из списка зависимостей. Линтер этого не позволит: ```js {8} const serverUrl = 'https://localhost:1234'; @@ -205,7 +205,7 @@ function ChatRoom({ roomId }) { И линтер будет прав! Поскольку `roomId` может меняться со временем, это приведет к ошибке в вашем коде. -**Чтобы удалить зависимость, "докажите" линтеру, что она *не нуждается* в том, чтобы быть зависимостью.** Например, вы можете переместить `roomId` за пределы вашего компонента, чтобы доказать, что он не реактивный и не изменится при повторных рендерингах: +**Чтобы удалить зависимость, «докажите» линтеру, что она *не нуждается* в том, чтобы быть зависимостью.** Например, вы можете переместить `roomId` за пределы вашего компонента, чтобы доказать, что он не является реактивным и не изменится при повторном рендеринге: ```js {2,9} const serverUrl = 'https://localhost:1234'; @@ -221,7 +221,7 @@ function ChatRoom() { } ``` -Теперь, поскольку `roomId` не является реактивным значением (и не может измениться при повторном рендеринге), оно не нуждается в том, чтобы быть зависимостью: +Теперь, когда `roomId` не является реактивным значением (и не может измениться при повторном рендеринге), ему не нужно быть зависимостью: @@ -263,23 +263,23 @@ button { margin-left: 10px; } -Вот почему вы теперь можете указать [пустой (`[]`) список зависимостей.](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) Ваш эффект *действительно не* зависит ни от какого реактивного значения, поэтому он *действительно не* нуждается в повторном запуске при изменении каких-либо пропсов или состояния компонента. +Вот почему теперь вы можете указать [пустой (`[]`) список зависимостей.](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) Ваш Effect *действительно не* зависит больше ни от какого реактивного значения, поэтому ему *действительно не нужно* перезапускаться, когда изменяются какие-либо пропсы или состояние компонента. -### Чтобы изменить зависимости, измените код {/*to-change-the-dependencies-change-the-code*/} +### Чтобы изменить зависимости, сначала измените код {/*to-change-the-dependencies-change-the-code*/} -Вы могли заметить закономерность в своем рабочем процессе: +Вы могли заметить закономерность в вашем рабочем процессе: -1. Сначала вы **изменяете код** вашего эффекта или способ объявления ваших реактивных значений. -2. Затем вы следуете за линтером и корректируете зависимости, чтобы они **соответствовали измененному вами коду.** -3. Если вас не устраивает список зависимостей, вы **возвращаетесь к первому шагу** (и снова изменяете код). +1. Сначала вы **изменяете код** вашего Effect или то, как объявляются ваши реактивные значения. +2. Затем вы следуете линтеру и настраиваете зависимости, чтобы **соответствовать коду, который вы изменили.** +3. Если вы не довольны списком зависимостей, вы **возвращаетесь к первому шагу** (и снова меняете код). -Последняя часть важна. **Если вы хотите изменить зависимости, сначала измените окружающий код.** Вы можете рассматривать список зависимостей как [список всех реактивных значений, используемых кодом вашего эффекта.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) Вы не *выбираете*, что поместить в этот список. Список *описывает* ваш код. Чтобы изменить список зависимостей, измените код. +Последняя часть важна. **Если вы хотите изменить зависимости, сначала измените окружающий код.** Вы можете думать о списке зависимостей как [о списке всех реактивных значений, используемых кодом вашего Effect.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) Вы не *выбираете*, что поместить в этот список. Список *описывает* ваш код. Чтобы изменить список зависимостей, измените код. -Это может показаться решением уравнения. Вы можете начать с цели (например, удалить зависимость), и вам нужно "найти" код, соответствующий этой цели. Не всем нравится решать уравнения, и то же самое можно сказать о написании эффектов! К счастью, ниже приведен список распространенных рецептов, которые вы можете попробовать. +Это может показаться решением уравнения. Вы можете начать с цели (например, удалить зависимость), и вам нужно «найти» код, соответствующий этой цели. Не всем нравится решать уравнения, и то же самое можно сказать о написании Effects! К счастью, ниже приведен список распространенных рецептов, которые вы можете попробовать. -Если у вас есть существующая кодовая база, у вас могут быть некоторые эффекты, которые подавляют линтер следующим образом: +Если у вас есть существующая кодовая база, у вас могут быть некоторые Effects, которые подавляют линтер, например: ```js {3-4} useEffect(() => { @@ -289,9 +289,9 @@ useEffect(() => { }, []); ``` -**Когда зависимости не соответствуют коду, существует очень высокий риск внесения ошибок.** Подавляя линтер, вы "лжете" React о значениях, от которых зависит ваш эффект. +**Когда зависимости не соответствуют коду, существует очень высокий риск внесения ошибок.** Подавляя линтер, вы «лжете» React о значениях, от которых зависит ваш Effect. -Вместо этого используйте приведенные ниже методы. +Вместо этого используйте методы, описанные ниже. @@ -348,31 +348,32 @@ button { margin: 10px; } -Допустим, вы хотели запустить эффект "только при монтировании". Вы прочитали, что [пустые (`[]`) зависимости](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) делают это, поэтому вы решили проигнорировать линтер и принудительно указали `[]` в качестве зависимостей. +Предположим, вы хотите запустить Effect «только при монтировании». Вы прочитали, что [пустые (`[]`) зависимости](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) делают это, поэтому вы решили игнорировать линтер и принудительно указали `[]` в качестве зависимостей. -Этот счетчик должен был увеличиваться каждую секунду на величину, настраиваемую двумя кнопками. Однако, поскольку вы "солгал" React, что этот эффект ни от чего не зависит, React навсегда сохраняет функцию `onTick` из начального рендеринга. [Во время этого рендеринга](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` был `0`, а `increment` был `1`. Вот почему `onTick` из этого рендеринга всегда вызывает `setCount(0 + 1)` каждую секунду, и вы всегда видите `1`. Ошибки, подобные этой, труднее исправить, когда они распределены по нескольким компонентам. +Этот счетчик должен был увеличиваться каждую секунду на величину, настраиваемую с помощью двух кнопок. Однако, поскольку вы «солгали» React, что этот Effect ни от чего не зависит, React навсегда продолжает использовать функцию `onTick` из начального рендеринга. [Во время этого рендеринга,](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` было `0`, а `increment` было `1`. Вот почему `onTick` из этого рендеринга всегда вызывает `setCount(0 + 1)` каждую секунду, и вы всегда видите `1`. Ошибки, подобные этой, труднее исправить, когда они разбросаны по нескольким компонентам. -Всегда есть лучшее решение, чем игнорировать линтер! Чтобы исправить этот код, вам нужно добавить `onTick` в список зависимостей. (Чтобы гарантировать, что интервал устанавливается только один раз, [сделайте `onTick` событием эффекта.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)) +Всегда есть лучшее решение, чем игнорирование линтера! Чтобы исправить этот код, вам нужно добавить `onTick` в список зависимостей. (Чтобы убедиться, что интервал настроен только один раз, [сделайте `onTick` событием Effect.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)) -**Мы рекомендуем относиться к ошибке линтера зависимостей как к ошибке компиляции. Если вы не подавите ее, вы никогда не увидите подобных ошибок.** Остальная часть этой страницы документирует альтернативы для этого и других случаев. +**Мы рекомендуем относиться к ошибке линта зависимостей как к ошибке компиляции. Если вы не подавите ее, вы никогда не увидите таких ошибок.** Остальная часть этой страницы документирует альтернативы для этого и других случаев. + ## Удаление ненужных зависимостей {/*removing-unnecessary-dependencies*/} -Каждый раз, когда вы корректируете зависимости эффекта, чтобы они отражали код, взгляните на список зависимостей. Имеет ли смысл, чтобы эффект перезапускался при изменении любой из этих зависимостей? Иногда ответ — «нет»: +Каждый раз, когда вы настраиваете зависимости Effect в соответствии с кодом, смотрите на список зависимостей. Имеет ли смысл, чтобы Effect перезапускался при изменении любой из этих зависимостей? Иногда ответ — «нет»: -* Вы можете захотеть перезапустить *различные части* вашего эффекта при разных условиях. -* Вы можете захотеть только прочитать *последнее значение* некоторой зависимости, вместо того чтобы «реагировать» на её изменения. -* Зависимость может слишком часто меняться *непреднамеренно*, потому что это объект или функция. +* Возможно, вы захотите повторно выполнить *разные части* вашего Effect при разных условиях. +* Возможно, вы захотите только прочитать *последнее значение* какой-либо зависимости, а не «реагировать» на ее изменения. +* Зависимость может меняться слишком часто *непреднамеренно*, потому что это объект или функция. -Чтобы найти правильное решение, вам нужно будет ответить на несколько вопросов о вашем эффекте. Давайте пройдемся по ним. +Чтобы найти правильное решение, вам нужно будет ответить на несколько вопросов о вашем Effect. Давайте рассмотрим их. -### Следует ли переместить этот код в обработчик событий? {/*should-this-code-move-to-an-event-handler*/} +### Должен ли этот код переместиться в обработчик событий? {/*should-this-code-move-to-an-event-handler*/} -Первое, о чем следует подумать, — это должен ли этот код вообще быть эффектом. +Первое, о чем вы должны подумать, — это должен ли этот код вообще быть Effect. -Представьте форму. При отправке вы устанавливаете переменную состояния `submitted` в `true`. Вам нужно отправить POST-запрос и показать уведомление. Вы поместили эту логику внутрь эффекта, который «реагирует» на `submitted`, равное `true`: +Представьте себе форму. При отправке вы устанавливаете переменную состояния `submitted` в `true`. Вам нужно отправить POST-запрос и показать уведомление. Вы поместили эту логику внутрь Effect, который «реагирует» на то, что `submitted` равно `true`: ```js {6-8} function Form() { @@ -380,7 +381,7 @@ function Form() { useEffect(() => { if (submitted) { - // 🔴 Избегайте: Логика, специфичная для событий, внутри эффекта + // 🔴 Избегайте: логика, специфичная для события, внутри Effect post('/api/register'); showNotification('Successfully registered!'); } @@ -394,7 +395,7 @@ function Form() { } ``` -Позже вы хотите стилизовать сообщение уведомления в соответствии с текущей темой, поэтому вы читаете текущую тему. Поскольку `theme` объявлена в теле компонента, это реактивное значение, поэтому вы добавляете её в зависимости: +Позже вы хотите стилизовать сообщение уведомления в соответствии с текущей темой, поэтому вы читаете текущую тему. Поскольку `theme` объявлена в теле компонента, это реактивное значение, поэтому вы добавляете его в качестве зависимости: ```js {3,9,11} function Form() { @@ -403,11 +404,11 @@ function Form() { useEffect(() => { if (submitted) { - // 🔴 Избегайте: Логика, специфичная для событий, внутри эффекта + // 🔴 Избегайте: логика, специфичная для события, внутри Effect post('/api/register'); showNotification('Successfully registered!', theme); } - }, [submitted, theme]); // ✅ Все зависимости объявлены + }, [submitted, theme]); // ✅ Объявлены все зависимости function handleSubmit() { setSubmitted(true); @@ -417,16 +418,16 @@ function Form() { } ``` -Сделав это, вы внесли ошибку. Представьте, что вы сначала отправляете форму, а затем переключаетесь между темной и светлой темами. `theme` изменится, эффект перезапустится, и поэтому он снова отобразит то же уведомление! +Сделав это, вы внесли ошибку. Представьте, что вы сначала отправляете форму, а затем переключаетесь между темной и светлой темами. `theme` изменится, Effect перезапустится, и поэтому он снова отобразит то же уведомление! -**Проблема здесь в том, что это вообще не должно быть эффектом.** Вы хотите отправить этот POST-запрос и показать уведомление в ответ на *отправку формы*, что является конкретным взаимодействием. Чтобы выполнить какой-либо код в ответ на конкретное взаимодействие, поместите эту логику непосредственно в соответствующий обработчик событий: +**Проблема здесь в том, что это вообще не должно быть Effect.** Вы хотите отправить этот POST-запрос и показать уведомление в ответ на *отправку формы*, что является конкретным взаимодействием. Чтобы запустить какой-то код в ответ на конкретное взаимодействие, поместите эту логику непосредственно в соответствующий обработчик событий: ```js {6-7} function Form() { const theme = useContext(ThemeContext); function handleSubmit() { - // ✅ Хорошо: Логика, специфичная для событий, вызывается из обработчиков событий + // ✅ Хорошо: логика, специфичная для события, вызывается из обработчиков событий post('/api/register'); showNotification('Successfully registered!', theme); } @@ -435,13 +436,13 @@ function Form() { } ``` -Теперь, когда код находится в обработчике событий, он не является реактивным — он будет выполняться только тогда, когда пользователь отправляет форму. Читайте больше о [выборе между обработчиками событий и эффектами](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) и о [том, как удалить ненужные эффекты.](/learn/you-might-not-need-an-effect) +Теперь, когда код находится в обработчике событий, он не является реактивным, поэтому он будет выполняться только тогда, когда пользователь отправляет форму. Узнайте больше о [выборе между обработчиками событий и Effects](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) и [о том, как удалить ненужные Effects.](/learn/you-might-not-need-an-effect) -### Ваш эффект выполняет несколько несвязанных действий? {/*is-your-effect-doing-several-unrelated-things*/} +### Выполняет ли ваш Effect несколько несвязанных действий? {/*is-your-effect-doing-several-unrelated-things*/} -Следующий вопрос, который вы должны себе задать, заключается в том, выполняет ли ваш эффект несколько несвязанных действий. +Следующий вопрос, который вы должны себе задать, — выполняет ли ваш Effect несколько несвязанных действий. -Представьте, что вы создаете форму доставки, где пользователю нужно выбрать город и район. Вы получаете список `cities` с сервера в соответствии с выбранной `country`, чтобы показать их в выпадающем списке: +Представьте, что вы создаете форму доставки, где пользователю нужно выбрать свой город и область. Вы получаете список `cities` с сервера в соответствии с выбранной `country`, чтобы отобразить их в раскрывающемся списке: ```js function ShippingForm({ country }) { @@ -460,14 +461,14 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country]); // ✅ Все зависимости объявлены + }, [country]); // ✅ Объявлены все зависимости // ... ``` -Это хороший пример [получения данных в эффекте.](/learn/you-might-not-need-an-effect#fetching-data) Вы синхронизируете состояние `cities` с сетью в соответствии с пропом `country`. Вы не можете сделать это в обработчике событий, потому что вам нужно получать данные, как только `ShippingForm` отобразится, и всякий раз, когда `country` изменится (независимо от того, какое взаимодействие это вызвало). +Это хороший пример [получения данных в Effect.](/learn/you-might-not-need-an-effect#fetching-data) Вы синхронизируете состояние `cities` с сетью в соответствии с пропсом `country`. Вы не можете сделать это в обработчике событий, потому что вам нужно получить данные, как только отобразится `ShippingForm`, и всякий раз, когда изменяется `country` (независимо от того, какое взаимодействие вызывает это). -Теперь давайте предположим, что вы добавляете второй выпадающий список для районов города, который должен получать `areas` для выбранного в данный момент `city`. Вы можете начать с добавления второго вызова `fetch` для списка районов внутри того же эффекта: +Теперь давайте предположим, что вы добавляете второй раскрывающийся список для областей города, который должен получать `areas` для выбранного в данный момент `city`. Вы можете начать с добавления второго вызова `fetch` для списка областей внутри того же Effect: ```js {15-24,28} function ShippingForm({ country }) { @@ -484,7 +485,7 @@ function ShippingForm({ country }) { setCities(json); } }); - // 🔴 Избегайте: Один эффект синхронизирует два независимых процесса + // 🔴 Избегайте: один Effect синхронизирует два независимых процесса if (city) { fetch(`/api/areas?city=${city}`) .then(response => response.json()) @@ -497,19 +498,19 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country, city]); // ✅ Все зависимости объявлены + }, [country, city]); // ✅ Объявлены все зависимости // ... ``` -Однако, поскольку эффект теперь использует переменную состояния `city`, вам пришлось добавить `city` в список зависимостей. Это, в свою очередь, привело к проблеме: когда пользователь выбирает другой город, эффект перезапустится и вызовет `fetchCities(country)`. В результате вы будете ненужно перезапрашивать список городов много раз. +Однако, поскольку Effect теперь использует переменную состояния `city`, вам пришлось добавить `city` в список зависимостей. Это, в свою очередь, привело к проблеме: когда пользователь выбирает другой город, Effect перезапустится и вызовет `fetchCities(country)`. В результате вы будете ненужно повторно получать список городов много раз. -**Проблема этого кода в том, что вы синхронизируете две разные несвязанные вещи:** +**Проблема с этим кодом в том, что вы синхронизируете две разные несвязанные вещи:** -1. Вы хотите синхронизировать состояние `cities` с сетью на основе пропа `country`. +1. Вы хотите синхронизировать состояние `cities` с сетью на основе пропса `country`. 1. Вы хотите синхронизировать состояние `areas` с сетью на основе состояния `city`. -Разделите логику на два эффекта, каждый из которых реагирует на проп, с которым ему нужно синхронизироваться: +Разделите логику на два Effects, каждый из которых реагирует на пропс, который ему необходимо синхронизировать: ```js {19-33} function ShippingForm({ country }) { @@ -526,7 +527,7 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country]); // ✅ Все зависимости объявлены + }, [country]); // ✅ Объявлены все зависимости const [city, setCity] = useState(null); const [areas, setAreas] = useState(null); @@ -544,18 +545,18 @@ function ShippingForm({ country }) { ignore = true; }; } - }, [city]); // ✅ Все зависимости объявлены + }, [city]); // ✅ Объявлены все зависимости // ... ``` -Теперь первый эффект перезапускается только в том случае, если `country` изменится, а второй эффект перезапускается, когда `city` изменится. Вы разделили их по назначению: две разные вещи синхронизируются двумя отдельными эффектами. Два отдельных эффекта имеют два отдельных списка зависимостей, поэтому они не будут непреднамеренно запускать друг друга. +Теперь первый Effect перезапускается только в том случае, если изменяется `country`, в то время как второй Effect перезапускается, когда изменяется `city`. Вы разделили их по назначению: два разных действия синхронизируются двумя отдельными Effects. Два отдельных Effects имеют два отдельных списка зависимостей, поэтому они не будут непреднамеренно запускать друг друга. -Итоговый код длиннее исходного, но разделение этих эффектов по-прежнему является правильным. [Каждый эффект должен представлять собой отдельный процесс синхронизации.](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) В этом примере удаление одного эффекта не нарушает логику другого. Это означает, что они *синхронизируют разные вещи*, и их хорошо разделять. Если вас беспокоит дублирование, вы можете улучшить этот код, [выделив повторяющуюся логику в пользовательский хук.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks) +Окончательный код длиннее исходного, но разделение этих Effects все равно правильно. [Каждый Effect должен представлять собой независимый процесс синхронизации.](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) В этом примере удаление одного Effect не нарушает логику другого Effect. Это означает, что они *синхронизируют разные вещи*, и их хорошо разделить. Если вы беспокоитесь о дублировании, вы можете улучшить этот код, [извлекая повторяющуюся логику в пользовательский Hook.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks) -### Вы читаете некоторое состояние для вычисления следующего состояния? {/*are-you-reading-some-state-to-calculate-the-next-state*/} +### Вы читаете какое-то состояние, чтобы вычислить следующее состояние? {/*are-you-reading-some-state-to-calculate-the-next-state*/} -Этот эффект обновляет переменную состояния `messages` новым созданным массивом каждый раз, когда приходит новое сообщение: +Этот Effect обновляет переменную состояния `messages` с помощью вновь созданного массива каждый раз, когда приходит новое сообщение: ```js {2,6-8} function ChatRoom({ roomId }) { @@ -569,7 +570,7 @@ function ChatRoom({ roomId }) { // ... ``` -Он использует переменную `messages` для [создания нового массива](/learn/updating-arrays-in-state), начиная со всех существующих сообщений и добавляя новое сообщение в конец. Однако, поскольку `messages` является реактивным значением, прочитанным эффектом, оно должно быть зависимостью: +Он использует переменную `messages`, чтобы [создать новый массив](/learn/updating-arrays-in-state), начиная со всех существующих сообщений и добавляя новое сообщение в конец. Однако, поскольку `messages` — это реактивное значение, считываемое Effect, оно должно быть зависимостью: ```js {7,10} function ChatRoom({ roomId }) { @@ -581,15 +582,15 @@ function ChatRoom({ roomId }) { setMessages([...messages, receivedMessage]); }); return () => connection.disconnect(); - }, [roomId, messages]); // ✅ Все зависимости объявлены + }, [roomId, messages]); // ✅ Объявлены все зависимости // ... ``` -И добавление `messages` в зависимости создает проблему. +И добавление `messages` в качестве зависимости создает проблему. -Каждый раз, когда вы получаете сообщение, `setMessages()` вызывает повторный рендеринг компонента с новым массивом `messages`, который включает полученное сообщение. Однако, поскольку этот эффект теперь зависит от `messages`, это *также* приведет к повторной синхронизации эффекта. Таким образом, каждое новое сообщение приведет к повторному подключению чата. Пользователю это не понравится! +Каждый раз, когда вы получаете сообщение, `setMessages()` заставляет компонент перерендериваться с новым массивом `messages`, который включает полученное сообщение. Однако, поскольку этот Effect теперь зависит от `messages`, это *также* повторно синхронизирует Effect. Таким образом, каждое новое сообщение заставит чат переподключиться. Пользователю это не понравится! -Чтобы устранить проблему, не читайте `messages` внутри эффекта. Вместо этого передайте [функцию обновления](/reference/react/useState#updating-state-based-on-the-previous-state) в `setMessages`: +Чтобы исправить проблему, не читайте `messages` внутри Effect. Вместо этого передайте [функцию обновления](/reference/react/useState#updating-state-based-on-the-previous-state) в `setMessages`: ```js {7,10} function ChatRoom({ roomId }) { @@ -601,11 +602,12 @@ function ChatRoom({ roomId }) { setMessages(msgs => [...msgs, receivedMessage]); }); return () => connection.disconnect(); - }, [roomId]); // ✅ Все зависимости объявлены + }, [roomId]); // ✅ Объявлены все зависимости // ... ``` -**Обратите внимание, что ваш эффект теперь вообще не читает переменную `messages`.** Вам нужно только передать функцию обновления, такую как `msgs => [...msgs, receivedMessage]`. React [помещает вашу функцию обновления в очередь](/learn/queueing-a-series-of-state-updates) и предоставит ей аргумент `msgs` во время следующего рендеринга. Вот почему сам эффект больше не должен зависеть от `messages`. В результате этого исправления получение сообщения чата больше не приведет к повторному подключению чата. +**Обратите внимание, как ваш Effect вообще не читает переменную `messages`.** Вам нужно только передать функцию обновления, например `msgs => [...msgs, receivedMessage]`. React [помещает вашу функцию обновления в очередь](/learn/queueing-a-series-of-state-updates) и предоставит ей аргумент `msgs` во время следующего рендеринга. Вот почему сам Effect больше не должен зависеть от `messages`. В результате этого исправления получение сообщения чата больше не будет приводить к повторному подключению чата. + ### Хотите прочитать значение, не «реагируя» на его изменения? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/} @@ -615,7 +617,7 @@ function ChatRoom({ roomId }) { -Предположим, вы хотите воспроизвести звук, когда пользователь получает новое сообщение, если только `isMuted` не равно `true`: +Предположим, вы хотите воспроизводить звук, когда пользователь получает новое сообщение, если только `isMuted` не равно `true`: ```js {3,10-12} function ChatRoom({ roomId }) { @@ -634,7 +636,7 @@ function ChatRoom({ roomId }) { // ... ``` -Поскольку ваш Effect теперь использует `isMuted` в своём коде, вы должны добавить его в зависимости: +Поскольку ваш Effect теперь использует `isMuted` в своем коде, вам нужно добавить его в зависимости: ```js {10,15} function ChatRoom({ roomId }) { @@ -651,13 +653,13 @@ function ChatRoom({ roomId }) { } }); return () => connection.disconnect(); - }, [roomId, isMuted]); // ✅ Все зависимости объявлены + }, [roomId, isMuted]); // ✅ Объявлены все зависимости // ... ``` -Проблема в том, что каждый раз, когда `isMuted` изменяется (например, когда пользователь нажимает переключатель "Без звука"), Effect будет повторно синхронизироваться и переподключаться к чату. Это нежелательный пользовательский опыт! (В этом примере даже отключение линтера не помогло бы — если вы это сделаете, `isMuted` "застрянет" со своим старым значением.) +Проблема в том, что каждый раз, когда `isMuted` меняется (например, когда пользователь нажимает переключатель «Muted»), Effect будет повторно синхронизироваться и переподключаться к чату. Это не желаемый пользовательский опыт! (В этом примере даже отключение линтера не сработает — если вы это сделаете, `isMuted` «застрянет» со своим старым значением.) -Чтобы решить эту проблему, вам нужно извлечь логику, которая не должна быть реактивной, из Effect. Вы не хотите, чтобы этот Effect «реагировал» на изменения `isMuted`. [Переместите эту нереактивную часть логики в Event Effect:](/learn/separating-events-from-effects#declaring-an-effect-event) +Чтобы решить эту проблему, вам нужно извлечь логику, которая не должна быть реактивной, из Effect. Вы не хотите, чтобы этот Effect «реагировал» на изменения в `isMuted`. [Переместите эту нереактивную часть логики в Effect Event:](/learn/separating-events-from-effects#declaring-an-effect-event) ```js {1,7-12,18,21} import { useState, useEffect, useEffectEvent } from 'react'; @@ -680,15 +682,15 @@ function ChatRoom({ roomId }) { onMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId]); // ✅ Все зависимости объявлены + }, [roomId]); // ✅ Объявлены все зависимости // ... ``` -Event Effects позволяют разделить Effect на реактивные части (которые должны «реагировать» на реактивные значения, такие как `roomId`, и их изменения) и нереактивные части (которые только считывают свои последние значения, например, `onMessage` считывает `isMuted`). **Теперь, когда вы считываете `isMuted` внутри Event Effect, оно больше не должно быть зависимостью вашего Effect.** В результате чат не будет переподключаться при включении и выключении настройки "Без звука", решая исходную проблему! +Effect Events позволяют разделить Effect на реактивные части (которые должны «реагировать» на реактивные значения, такие как `roomId`, и их изменения) и нереактивные части (которые только читают свои последние значения, например, `onMessage` читает `isMuted`). **Теперь, когда вы читаете `isMuted` внутри Effect Event, ему не нужно быть зависимостью вашего Effect.** В результате чат не будет переподключаться при включении и выключении настройки «Muted», что решает исходную проблему! -#### Обертывание обработчика событий из пропсов {/*wrapping-an-event-handler-from-the-props*/} +#### Оборачивание обработчика событий из пропсов {/*wrapping-an-event-handler-from-the-props*/} -Вы можете столкнуться с похожей проблемой, когда ваш компонент получает обработчик событий в виде пропса: +Вы можете столкнуться с аналогичной проблемой, когда ваш компонент получает обработчик событий в качестве пропа: ```js {1,8,11} function ChatRoom({ roomId, onReceiveMessage }) { @@ -701,11 +703,11 @@ function ChatRoom({ roomId, onReceiveMessage }) { onReceiveMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId, onReceiveMessage]); // ✅ Все зависимости объявлены + }, [roomId, onReceiveMessage]); // ✅ Объявлены все зависимости // ... ``` -Предположим, родительский компонент передаёт *разную* функцию `onReceiveMessage` при каждом рендере: +Предположим, что родительский компонент передаёт *другую* функцию `onReceiveMessage` при каждом рендере: ```js {3-5} ``` -Поскольку `onReceiveMessage` является зависимостью, это вызовет повторную синхронизацию Effect после каждого рендера родителя. Это приведёт к повторному подключению к чату. Чтобы решить эту проблему, оберните вызов в Event Effect: +Поскольку `onReceiveMessage` является зависимостью, это приведёт к повторной синхронизации Effect после каждого повторного рендеринга родителя. Это заставит его переподключиться к чату. Чтобы решить эту проблему, оберните вызов в Effect Event: ```js {4-6,12,15} function ChatRoom({ roomId, onReceiveMessage }) { @@ -733,17 +735,17 @@ function ChatRoom({ roomId, onReceiveMessage }) { onMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId]); // ✅ Все зависимости объявлены + }, [roomId]); // ✅ Объявлены все зависимости // ... ``` -Event Effects не являются реактивными, поэтому вам не нужно указывать их как зависимости. В результате чат больше не будет переподключаться, даже если родительский компонент передаёт функцию, которая отличается при каждом рендере. +Effect Events не являются реактивными, поэтому вам не нужно указывать их в качестве зависимостей. В результате чат больше не будет переподключаться, даже если родительский компонент передаёт функцию, которая отличается при каждом повторном рендеринге. #### Разделение реактивного и нереактивного кода {/*separating-reactive-and-non-reactive-code*/} -В этом примере вы хотите регистрировать посещение каждый раз, когда изменяется `roomId`. Вы хотите включить текущее `notificationCount` в каждый лог, но вы *не* хотите, чтобы изменение `notificationCount` вызывало событие лога. +В этом примере вы хотите регистрировать посещение каждый раз, когда `roomId` меняется. Вы хотите включить текущий `notificationCount` с каждой записью, но вы *не* хотите, чтобы изменение `notificationCount` вызывало событие регистрации. -Решение снова заключается в разделении нереактивного кода на Event Effect: +Решение состоит в том, чтобы снова разделить нереактивный код на Effect Event: ```js {2-4,7} function Chat({ roomId, notificationCount }) { @@ -753,16 +755,17 @@ function Chat({ roomId, notificationCount }) { useEffect(() => { onVisit(roomId); - }, [roomId]); // ✅ Все зависимости объявлены + }, [roomId]); // ✅ Объявлены все зависимости // ... } ``` -Вы хотите, чтобы ваша логика была реактивной по отношению к `roomId`, поэтому вы считываете `roomId` внутри вашего Effect. Однако вы не хотите, чтобы изменение `notificationCount` регистрировало дополнительное посещение, поэтому вы считываете `notificationCount` внутри Event Effect. [Узнайте больше о чтении последних пропсов и состояния из Effects с помощью Event Effects.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) +Вы хотите, чтобы ваша логика была реактивной по отношению к `roomId`, поэтому вы читаете `roomId` внутри вашего Effect. Однако вы не хотите, чтобы изменение `notificationCount` регистрировало дополнительное посещение, поэтому вы читаете `notificationCount` внутри Effect Event. [Узнайте больше о чтении последних пропсов и состояния из Effects с помощью Effect Events.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) -### Значение реактивного значения меняется непреднамеренно? {/*does-some-reactive-value-change-unintentionally*/} -Иногда вы *хотите*, чтобы ваш Effect «реагировал» на определённое значение, но это значение меняется чаще, чем вам хотелось бы, и, возможно, не отражает реальных изменений с точки зрения пользователя. Например, предположим, вы создаёте объект `options` в теле вашего компонента, а затем читаете этот объект внутри вашего Effect: +### Непреднамеренное изменение реактивного значения? {/*does-some-reactive-value-change-unintentionally*/} + +Иногда вы *действительно* хотите, чтобы ваш Effect «реагировал» на определенное значение, но это значение меняется чаще, чем вам хотелось бы, и может не отражать никаких фактических изменений с точки зрения пользователя. Например, предположим, что вы создаете объект `options` в теле вашего компонента, а затем считываете этот объект изнутри вашего Effect: ```js {3-6,9} function ChatRoom({ roomId }) { @@ -778,7 +781,7 @@ function ChatRoom({ roomId }) { // ... ``` -Этот объект объявлен в теле компонента, поэтому он является [реактивным значением.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Когда вы читаете реактивное значение таким образом внутри Effect, вы объявляете его как зависимость. Это гарантирует, что ваш Effect будет «реагировать» на его изменения: +Этот объект объявлен в теле компонента, поэтому он является [реактивным значением.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Когда вы считываете реактивное значение, подобное этому, внутри Effect, вы объявляете его в качестве зависимости. Это гарантирует, что ваш Effect «реагирует» на его изменения: ```js {3,6} // ... @@ -786,11 +789,11 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [options]); // ✅ Все зависимости объявлены + }, [options]); // ✅ Объявлены все зависимости // ... ``` -Важно объявить его как зависимость! Это гарантирует, например, что если `roomId` изменится, ваш Effect переподключится к чату с новыми `options`. Однако в коде выше есть и проблема. Чтобы увидеть её, попробуйте ввести текст в поле ввода в песочнице ниже и посмотрите, что происходит в консоли: +Важно объявить его в качестве зависимости! Это гарантирует, например, что если `roomId` изменится, ваш Effect повторно подключится к чату с новыми `options`. Однако с приведенным выше кодом также есть проблема. Чтобы увидеть ее, попробуйте что-нибудь напечатать в поле ввода в песочнице ниже и посмотрите, что произойдет в консоли: @@ -803,7 +806,7 @@ const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); - // Временно отключим линтер, чтобы продемонстрировать проблему + // Временно отключите линтер, чтобы продемонстрировать проблему // eslint-disable-next-line react-hooks/exhaustive-deps const options = { serverUrl: serverUrl, @@ -848,7 +851,7 @@ export default function App() { ```js src/chat.js export function createConnection({ serverUrl, roomId }) { - // A real implementation would actually connect to the server + // Реальная реализация на самом деле подключится к серверу return { connect() { console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); @@ -867,11 +870,11 @@ button { margin-left: 10px; } -В песочнице выше поле ввода только обновляет переменную состояния `message`. С точки зрения пользователя, это не должно влиять на подключение к чату. Однако каждый раз, когда вы обновляете `message`, ваш компонент перерисовывается. Когда ваш компонент перерисовывается, код внутри него выполняется заново с самого начала. +В приведенной выше песочнице поле ввода обновляет только переменную состояния `message`. С точки зрения пользователя это не должно влиять на подключение к чату. Однако каждый раз, когда вы обновляете `message`, ваш компонент перерендеривается. Когда ваш компонент перерендеривается, код внутри него запускается снова с нуля. -Новый объект `options` создаётся с нуля при каждой перерисовке компонента `ChatRoom`. React видит, что объект `options` — это *другой объект*, отличный от объекта `options`, созданного во время предыдущего рендеринга. Именно поэтому он повторно синхронизирует ваш Effect (который зависит от `options`), и чат переподключается по мере ввода текста. +Новый объект `options` создается с нуля при каждом перерендеринге компонента `ChatRoom`. React видит, что объект `options` — это *другой объект*, чем объект `options`, созданный во время последнего рендеринга. Вот почему он повторно синхронизирует ваш Effect (который зависит от `options`), и чат переподключается по мере ввода текста. -**Эта проблема затрагивает только объекты и функции. В JavaScript каждый вновь созданный объект и функция считаются отличными от всех остальных. Неважно, что содержимое внутри них может быть одинаковым!** +**Эта проблема затрагивает только объекты и функции. В JavaScript каждый вновь созданный объект и функция считаются отличными от всех остальных. Не имеет значения, что содержимое внутри них может быть одинаковым!** ```js {7-8} // Во время первого рендеринга @@ -884,9 +887,9 @@ const options2 = { serverUrl: 'https://localhost:1234', roomId: 'music' }; console.log(Object.is(options1, options2)); // false ``` -**Зависимости от объектов и функций могут заставлять ваш Effect синхронизироваться чаще, чем вам нужно.** +**Зависимости от объектов и функций могут приводить к повторной синхронизации вашего Effect чаще, чем вам нужно.** -Вот почему, когда это возможно, вам следует избегать объектов и функций в качестве зависимостей вашего Effect. Вместо этого попробуйте переместить их за пределы компонента, внутрь Effect или извлечь примитивные значения из них. +Вот почему, когда это возможно, следует стараться избегать объектов и функций в качестве зависимостей вашего Effect. Вместо этого попробуйте переместить их за пределы компонента, внутрь Effect или извлечь из них примитивные значения. #### Переместите статические объекты и функции за пределы вашего компонента {/*move-static-objects-and-functions-outside-your-component*/} @@ -905,11 +908,11 @@ function ChatRoom() { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, []); // ✅ Все зависимости объявлены + }, []); // ✅ Объявлены все зависимости // ... ``` -Таким образом, вы *доказываете* линтеру, что это не реактивное значение. Оно не может измениться в результате перерисовки, поэтому ему не нужно быть зависимостью. Теперь перерисовка `ChatRoom` не вызовет повторную синхронизацию вашего Effect. +Таким образом, вы *доказываете* линтеру, что он не реактивный. Он не может измениться в результате перерендеринга, поэтому он не должен быть зависимостью. Теперь перерендеринг `ChatRoom` не приведет к повторной синхронизации вашего Effect. Это работает и для функций: @@ -929,15 +932,15 @@ function ChatRoom() { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, []); // ✅ Все зависимости объявлены + }, []); // ✅ Объявлены все зависимости // ... ``` -Поскольку `createOptions` объявлена за пределами вашего компонента, она не является реактивным значением. Вот почему её не нужно указывать в зависимостях вашего Effect, и почему она никогда не вызовет повторную синхронизацию вашего Effect. +Поскольку `createOptions` объявлена за пределами вашего компонента, она не является реактивным значением. Вот почему ее не нужно указывать в зависимостях вашего Effect, и почему она никогда не приведет к повторной синхронизации вашего Effect. #### Переместите динамические объекты и функции внутрь вашего Effect {/*move-dynamic-objects-and-functions-inside-your-effect*/} -Если ваш объект зависит от какого-либо реактивного значения, которое может измениться в результате перерисовки, например, пропса `roomId`, вы не можете вынести его *за пределы* компонента. Однако вы можете переместить его создание *внутрь* кода вашего Effect: +Если ваш объект зависит от какого-либо реактивного значения, которое может измениться в результате перерендеринга, например, пропса `roomId`, вы не можете вынести его *за пределы* вашего компонента. Однако вы можете переместить его создание *внутрь* кода вашего Effect: ```js {7-10,11,14} const serverUrl = 'https://localhost:1234'; @@ -953,11 +956,11 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ Все зависимости объявлены + }, [roomId]); // ✅ Объявлены все зависимости // ... ``` -Теперь, когда `options` объявлен внутри вашего Effect, он больше не является зависимостью вашего Effect. Вместо этого единственным реактивным значением, используемым вашим Effect, является `roomId`. Поскольку `roomId` не является объектом или функцией, вы можете быть уверены, что он не будет *непреднамеренно* отличаться. В JavaScript числа и строки сравниваются по их содержимому: +Теперь, когда `options` объявлен внутри вашего Effect, он больше не является зависимостью вашего Effect. Вместо этого единственным реактивным значением, используемым вашим Effect, является `roomId`. Поскольку `roomId` не является объектом или функцией, вы можете быть уверены, что он не будет *непреднамеренно* другим. В JavaScript числа и строки сравниваются по их содержимому: ```js {7-8} // Во время первого рендеринга @@ -1025,7 +1028,7 @@ export default function App() { ```js src/chat.js export function createConnection({ serverUrl, roomId }) { - // A real implementation would actually connect to the server + // Реальная реализация на самом деле подключится к серверу return { connect() { console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); @@ -1044,7 +1047,7 @@ button { margin-left: 10px; } -Однако он *переподключается*, когда вы меняете выпадающий список `roomId`, как и ожидалось. +Однако он *действительно* переподключается, когда вы меняете выпадающий список `roomId`, как и ожидалось. Это работает и для функций: @@ -1066,13 +1069,13 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ Все зависимости объявлены + }, [roomId]); // ✅ Объявлены все зависимости // ... ``` -Вы можете писать свои собственные функции для группировки частей логики внутри вашего Effect. Пока вы также объявляете их *внутри* вашего Effect, они не являются реактивными значениями, и поэтому им не нужно быть зависимостями вашего Effect. +Вы можете написать свои собственные функции для группировки частей логики внутри вашего Effect. Пока вы также объявляете их *внутри* вашего Effect, они не являются реактивными значениями, поэтому они не должны быть зависимостями вашего Effect. -#### Читайте примитивные значения из объектов {/*read-primitive-values-from-objects*/} +#### Считывайте примитивные значения из объектов {/*read-primitive-values-from-objects*/} Иногда вы можете получать объект из пропсов: @@ -1084,11 +1087,11 @@ function ChatRoom({ options }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [options]); // ✅ Все зависимости объявлены + }, [options]); // ✅ Объявлены все зависимости // ... ``` -Риск здесь в том, что родительский компонент создаст объект во время рендеринга: +Риск здесь заключается в том, что родительский компонент будет создавать объект во время рендеринга: ```js {3-6} ``` -Это приведёт к тому, что ваш Effect будет переподключаться при каждой перерисовке родительского компонента. Чтобы исправить это, читайте информацию из объекта *вне* Effect и избегайте зависимостей от объектов и функций: +Это привело бы к повторному подключению вашего Effect каждый раз, когда родительский компонент перерендеривается. Чтобы исправить это, считывайте информацию из объекта *за пределами* Effect и избегайте зависимостей от объектов и функций: ```js {4,7-8,12} function ChatRoom({ options }) { @@ -1114,15 +1117,15 @@ function ChatRoom({ options }) { }); connection.connect(); return () => connection.disconnect(); - }, [roomId, serverUrl]); // ✅ Все зависимости объявлены + }, [roomId, serverUrl]); // ✅ Объявлены все зависимости // ... ``` -Логика становится немного повторяющейся (вы читаете некоторые значения из объекта вне Effect, а затем создаёте объект с теми же значениями внутри Effect). Но это делает очень явным, от какой информации ваш Effect *фактически* зависит. Если объект непреднамеренно пересоздаётся родительским компонентом, чат не будет переподключаться. Однако, если `options.roomId` или `options.serverUrl` действительно отличаются, чат переподключится. +Логика становится немного повторяющейся (вы считываете некоторые значения из объекта за пределами Effect, а затем создаете объект с теми же значениями внутри Effect). Но это делает очень явным, от какой информации *фактически* зависит ваш Effect. Если объект непреднамеренно пересоздается родительским компонентом, чат не будет переподключаться. Однако, если `options.roomId` или `options.serverUrl` действительно отличаются, чат переподключится. #### Вычисляйте примитивные значения из функций {/*calculate-primitive-values-from-functions*/} -Тот же подход может работать и для функций. Например, предположим, родительский компонент передаёт функцию: +Тот же подход может работать и для функций. Например, предположим, что родительский компонент передает функцию: ```js {3-8} ``` -Чтобы избежать её включения в зависимости (и, как следствие, повторных переподключений при перерисовках), вызовите её вне Effect. Это даст вам значения `roomId` и `serverUrl`, которые не являются объектами и которые вы можете читать внутри вашего Effect: +Чтобы избежать превращения ее в зависимость (и вызывать повторное подключение при перерендеринге), вызовите ее за пределами Effect. Это даст вам значения `roomId` и `serverUrl`, которые не являются объектами и которые вы можете считывать изнутри вашего Effect: ```js {1,4} function ChatRoom({ getOptions }) { @@ -1150,21 +1153,21 @@ function ChatRoom({ getOptions }) { }); connection.connect(); return () => connection.disconnect(); - }, [roomId, serverUrl]); // ✅ Все зависимости объявлены + }, [roomId, serverUrl]); // ✅ Объявлены все зависимости // ... ``` -Это работает только для [чистых](/learn/keeping-components-pure) функций, поскольку их безопасно вызывать во время рендеринга. Если ваша функция является обработчиком событий, но вы не хотите, чтобы её изменения вызывали повторную синхронизацию вашего Effect, [оберните её в Effect Event вместо этого.](#do-you-want-to-read-a-value-without-reacting-to-its-changes) +Это работает только для [чистых](/learn/keeping-components-pure) функций, потому что их безопасно вызывать во время рендеринга. Если ваша функция является обработчиком событий, но вы не хотите, чтобы ее изменения повторно синхронизировали ваш Effect, [оберните ее вместо этого в Effect Event.](#do-you-want-to-read-a-value-without-reacting-to-its-changes) - Зависимости всегда должны соответствовать коду. -- Когда вы недовольны своими зависимостями, вам нужно редактировать код. -- Подавление линтера приводит к очень запутанным ошибкам, и вам всегда следует его избегать. +- Когда вы недовольны своими зависимостями, вам нужно отредактировать код. +- Подавление линтера приводит к очень запутанным ошибкам, и вам всегда следует избегать этого. - Чтобы удалить зависимость, вам нужно «доказать» линтеру, что она не нужна. -- Если какой-то код должен выполняться в ответ на определённое взаимодействие, переместите этот код в обработчик событий. -- Если разные части вашего Effect должны перезапускаться по разным причинам, разделите его на несколько Effect. -- Если вы хотите обновить состояние на основе предыдущего состояния, передайте функцию-обновлятор. +- Если какой-то код должен выполняться в ответ на определенное взаимодействие, переместите этот код в обработчик событий. +- Если разные части вашего Effect должны перезапускаться по разным причинам, разделите его на несколько Effects. +- Если вы хотите обновить какое-либо состояние на основе предыдущего состояния, передайте функцию обновления. - Если вы хотите прочитать последнее значение, не «реагируя» на него, извлеките Effect Event из вашего Effect. - В JavaScript объекты и функции считаются разными, если они были созданы в разное время. - Старайтесь избегать зависимостей от объектов и функций. Переместите их за пределы компонента или внутрь Effect. @@ -1173,9 +1176,10 @@ function ChatRoom({ getOptions }) { -#### Исправление сбрасываемого интервала {/*fix-a-resetting-interval*/} -Этот эффект настраивает интервал, который срабатывает каждую секунду. Вы заметили странное поведение: кажется, что интервал уничтожается и воссоздается каждый раз, когда срабатывает. Исправьте код так, чтобы интервал не воссоздавался постоянно. +#### Исправьте сбрасывающийся интервал {/*fix-a-resetting-interval*/} + +Этот эффект настраивает интервал, который срабатывает каждую секунду. Вы заметили странную вещь: кажется, что интервал уничтожается и пересоздается каждый раз, когда срабатывает. Исправьте код, чтобы интервал не пересоздавался постоянно. @@ -1192,18 +1196,18 @@ export default function Timer() { const [count, setCount] = useState(0); useEffect(() => { - console.log('✅ Creating an interval'); + console.log('✅ Создание интервала'); const id = setInterval(() => { - console.log('⏰ Interval tick'); + console.log('⏰ Тик интервала'); setCount(count + 1); }, 1000); return () => { - console.log('❌ Clearing an interval'); + console.log('❌ Очистка интервала'); clearInterval(id); }; }, [count]); - return

Counter: {count}

+ return

Счётчик: {count}

} ``` @@ -1211,7 +1215,7 @@ export default function Timer() { -Вы хотите обновить состояние `count` до значения `count + 1` изнутри эффекта. Однако это делает ваш эффект зависимым от `count`, который изменяется при каждом тике, и именно поэтому ваш интервал воссоздается при каждом тике. +Вы хотите обновить состояние `count`, чтобы оно было `count + 1` изнутри эффекта. Однако это делает ваш эффект зависимым от `count`, который изменяется с каждым тиком, и поэтому ваш интервал пересоздается при каждом тике. Чтобы решить эту проблему, используйте [функцию обновления](/reference/react/useState#updating-state-based-on-the-previous-state) и напишите `setCount(c => c + 1)` вместо `setCount(count + 1)`: @@ -1224,36 +1228,36 @@ export default function Timer() { const [count, setCount] = useState(0); useEffect(() => { - console.log('✅ Creating an interval'); + console.log('✅ Создание интервала'); const id = setInterval(() => { - console.log('⏰ Interval tick'); + console.log('⏰ Тик интервала'); setCount(c => c + 1); }, 1000); return () => { - console.log('❌ Clearing an interval'); + console.log('❌ Очистка интервала'); clearInterval(id); }; }, []); - return

Counter: {count}

+ return

Счётчик: {count}

} ``` -Вместо того чтобы считывать `count` внутри эффекта, вы передаете инструкцию `c => c + 1` («увеличь это число!») в React. React применит ее при следующем рендеринге. И поскольку вам больше не нужно считывать значение `count` внутри вашего эффекта, вы можете оставить зависимости эффекта пустыми (`[]`). Это предотвращает повторное создание интервала эффектом при каждом тике. +Вместо чтения `count` внутри эффекта, вы передаете инструкцию `c => c + 1` («увеличить это число!») в React. React применит ее при следующем рендере. И поскольку вам больше не нужно читать значение `count` внутри вашего эффекта, вы можете оставить зависимости вашего эффекта пустыми (`[]`). Это предотвращает пересоздание интервала вашим эффектом при каждом тике.
-#### Исправление повторно запускаемой анимации {/*fix-a-retriggering-animation*/} +#### Исправьте повторное срабатывание анимации {/*fix-a-retriggering-animation*/} -В этом примере при нажатии кнопки «Показать» появляется всплывающее сообщение. Анимация занимает одну секунду. При нажатии кнопки «Удалить» сообщение немедленно исчезает. Логика анимации появления реализована в файле `animation.js` как обычный JavaScript [цикл анимации.](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) Вам не нужно менять эту логику. Вы можете рассматривать ее как стороннюю библиотеку. Ваш эффект создает экземпляр `FadeInAnimation` для DOM-узла, а затем вызывает `start(duration)` или `stop()`, чтобы управлять анимацией. Длительность контролируется ползунком. Настройте ползунок и посмотрите, как меняется анимация. +В этом примере, когда вы нажимаете «Показать», появляется приветственное сообщение. Анимация занимает секунду. Когда вы нажимаете «Удалить», приветственное сообщение сразу исчезает. Логика анимации появления реализована в файле `animation.js` в виде простого JavaScript [цикла анимации.](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) Вам не нужно изменять эту логику. Вы можете рассматривать ее как стороннюю библиотеку. Ваш эффект создает экземпляр `FadeInAnimation` для узла DOM, а затем вызывает `start(duration)` или `stop()` для управления анимацией. `duration` управляется ползунком. Настройте ползунок и посмотрите, как меняется анимация. -Этот код уже работает, но есть кое-что, что вы хотите изменить. В настоящее время, когда вы перемещаете ползунок, который контролирует переменную состояния `duration`, это повторно запускает анимацию. Измените поведение так, чтобы эффект не «реагировал» на переменную `duration`. Когда вы нажимаете «Показать», эффект должен использовать текущее значение `duration` на ползунке. Однако само перемещение ползунка не должно само по себе повторно запускать анимацию. +Этот код уже работает, но есть кое-что, что вы хотите изменить. В настоящее время, когда вы перемещаете ползунок, который управляет переменной состояния `duration`, это повторно запускает анимацию. Измените поведение так, чтобы эффект не «реагировал» на переменную `duration`. Когда вы нажимаете «Показать», эффект должен использовать текущее значение `duration` на ползунке. Однако перемещение самого ползунка не должно само по себе повторно запускать анимацию. -Есть ли в коде эффекта строка, которая не должна быть реактивной? Как вы можете вынести нереактивный код из эффекта? +Есть ли строка кода внутри эффекта, которая не должна быть реактивной? Как можно переместить нереактивный код из эффекта? @@ -1382,7 +1386,7 @@ html, body { min-height: 300px; } -Вашему эффекту необходимо считывать последнее значение `duration`, но вы не хотите, чтобы он «реагировал» на изменения `duration`. Вы используете `duration` для запуска анимации, но запуск анимации не является реактивным. Вынесите нереактивную строку кода в Event Effect и вызовите эту функцию из вашего эффекта. +Вашему эффекту необходимо прочитать последнее значение `duration`, но вы не хотите, чтобы он «реагировал» на изменения в `duration`. Вы используете `duration`, чтобы запустить анимацию, но запуск анимации не является реактивным. Извлеките нереактивную строку кода в событие эффекта и вызовите эту функцию из вашего эффекта. @@ -1505,19 +1509,20 @@ html, body { min-height: 300px; } -Event Effects, такие как `onAppear`, не являются реактивными, поэтому вы можете считывать `duration` внутри них, не вызывая повторного запуска анимации. +События эффекта, такие как `onAppear`, не являются реактивными, поэтому вы можете прочитать `duration` внутри, не перезапуская анимацию. + #### Исправьте переподключение чата {/*fix-a-reconnecting-chat*/} -В этом примере каждый раз при нажатии кнопки «Toggle theme» чат переподключается. Почему это происходит? Исправьте ошибку так, чтобы чат переподключался только при изменении URL сервера или выборе другой комнаты чата. +В этом примере каждый раз, когда вы нажимаете «Toggle theme», чат переподключается. Почему это происходит? Исправьте ошибку, чтобы чат переподключался только при изменении URL-адреса сервера или выборе другой комнаты чата. -Считайте `chat.js` внешней сторонней библиотекой: вы можете обращаться к ней для проверки её API, но не редактируйте её. +Рассматривайте `chat.js` как внешнюю стороннюю библиотеку: вы можете обращаться к ней, чтобы проверить ее API, но не редактируйте ее. -Существует несколько способов исправить это, но в конечном итоге вам следует избегать использования объекта в качестве зависимости. +Есть несколько способов исправить это, но в конечном итоге вам нужно избегать использования объекта в качестве зависимости. @@ -1611,9 +1616,9 @@ label, button { display: block; margin-bottom: 5px; } -Ваш эффект перезапускается, потому что он зависит от объекта `options`. Объекты могут быть созданы непреднамеренно, поэтому старайтесь избегать их в качестве зависимостей ваших эффектов, когда это возможно. +Ваш Effect перезапускается, потому что он зависит от объекта `options`. Объекты могут быть непреднамеренно пересозданы, поэтому вам следует избегать их использования в качестве зависимостей ваших Effects, когда это возможно. -Наименее инвазивным исправлением является чтение `roomId` и `serverUrl` непосредственно перед эффектом и затем установка эффекта в зависимость от этих примитивных значений (которые не могут быть изменены непреднамеренно). Внутри эффекта создайте объект и передайте его в `createConnection`: +Наименее инвазивное исправление — прочитать `roomId` и `serverUrl` прямо за пределами Effect, а затем сделать Effect зависимым от этих примитивных значений (которые не могут измениться непреднамеренно). Внутри Effect создайте объект и передайте его в `createConnection`: @@ -1707,7 +1712,7 @@ label, button { display: block; margin-bottom: 5px; } -Было бы еще лучше заменить объектный проп `options` на более конкретные пропы `roomId` и `serverUrl`: +Еще лучше заменить проп `options` объекта на более конкретные пропсы `roomId` и `serverUrl`: @@ -1798,25 +1803,25 @@ label, button { display: block; margin-bottom: 5px; } -Использование примитивных пропсов, где это возможно, упрощает последующую оптимизацию компонентов. +Придерживаясь примитивных пропсов, где это возможно, упрощается оптимизация ваших компонентов в дальнейшем. -#### Исправляем переподключающийся чат {/*fix-a-reconnecting-chat-again*/} +#### Исправьте повторное подключение к чату, снова {/*fix-a-reconnecting-chat-again*/} -Этот пример подключается к чату с шифрованием или без него. Переключите флажок и обратите внимание на разные сообщения в консоли при включенном и выключенном шифровании. Попробуйте сменить комнату. Затем попробуйте переключить тему. Когда вы подключены к комнате чата, вы будете получать новые сообщения каждые несколько секунд. Убедитесь, что их цвет соответствует выбранной вами теме. +Этот пример подключается к чату с шифрованием или без него. Переключите флажок и обратите внимание на разные сообщения в консоли, когда шифрование включено и выключено. Попробуйте сменить комнату. Затем попробуйте переключить тему. Когда вы подключены к чат-комнате, вы будете получать новые сообщения каждые несколько секунд. Убедитесь, что их цвет соответствует выбранной вами теме. -В этом примере чат переподключается каждый раз, когда вы пытаетесь сменить тему. Исправьте это. После исправления смена темы не должна переподключать чат, но переключение настроек шифрования или смена комнаты должны вызывать переподключение. +В этом примере чат переподключается каждый раз, когда вы пытаетесь изменить тему. Исправьте это. После исправления изменение темы не должно приводить к повторному подключению чата, но переключение настроек шифрования или изменение комнаты должно приводить к повторному подключению. -Не меняйте код в `chat.js`. Кроме этого, вы можете изменять любой код, если это приведет к такому же поведению. Например, вам может быть полезно изменить передаваемые пропсы. +Не изменяйте код в `chat.js`. В остальном вы можете изменить любой код, если это приводит к тому же поведению. Например, вам может быть полезно изменить, какие пропсы передаются вниз. -Вы передаете две функции: `onMessage` и `createConnection`. Обе они создаются с нуля при каждом перерендере `App`. Они считаются новыми значениями каждый раз, поэтому повторно запускают ваш эффект. +Вы передаете две функции: `onMessage` и `createConnection`. Обе они создаются с нуля каждый раз, когда `App` перерендеривается. Они считаются новыми значениями каждый раз, поэтому они повторно запускают ваш Effect. -Одна из этих функций является обработчиком событий. Вы знаете какой-нибудь способ вызвать обработчик событий в эффекте без "реакции" на новые значения функции обработчика? Это было бы полезно! +Одна из этих функций является обработчиком событий. Знаете ли вы какой-нибудь способ вызвать обработчик событий в Effect, не «реагируя» на новые значения функции обработчика событий? Это пригодится! -Одна из этих функций существует только для передачи некоторого состояния импортированному методу API. Действительно ли эта функция необходима? Какая существенная информация передается? Возможно, вам потребуется переместить некоторые импорты из `App.js` в `ChatRoom.js`. +Другая из этих функций существует только для передачи некоторого состояния в импортированный метод API. Действительно ли эта функция необходима? Какая существенная информация передается вниз? Возможно, вам потребуется переместить некоторые импорты из `App.js` в `ChatRoom.js`. @@ -2031,11 +2036,11 @@ label, button { display: block; margin-bottom: 5px; } -Существует более одного правильного способа решить эту проблему, но вот одно из возможных решений. +Существует более одного правильного способа решения этой задачи, но вот одно из возможных решений. -В исходном примере переключение темы приводило к созданию и передаче разных функций `onMessage` и `createConnection`. Поскольку эффект зависел от этих функций, чат переподключался каждый раз при переключении темы. +В исходном примере переключение темы приводило к созданию и передаче разных функций `onMessage` и `createConnection`. Поскольку Effect зависел от этих функций, чат переподключался каждый раз, когда вы переключали тему. -Чтобы исправить проблему с `onMessage`, вам нужно было обернуть ее в событие эффекта (Effect Event): +Чтобы исправить проблему с `onMessage`, вам нужно было обернуть его в Effect Event: ```js {1,2,6} export default function ChatRoom({ roomId, createConnection, onMessage }) { @@ -2047,9 +2052,9 @@ export default function ChatRoom({ roomId, createConnection, onMessage }) { // ... ``` -В отличие от пропса `onMessage`, событие эффекта `onReceiveMessage` не является реактивным. Поэтому оно не должно быть зависимостью вашего эффекта. В результате изменения `onMessage` не вызовут переподключение чата. +В отличие от пропса `onMessage`, Effect Event `onReceiveMessage` не является реактивным. Вот почему он не должен быть зависимостью вашего Effect. В результате изменения `onMessage` не приведут к повторному подключению чата. -Вы не можете сделать то же самое с `createConnection`, потому что оно *должно* быть реактивным. Вы *хотите*, чтобы эффект повторно запускался, если пользователь переключается между зашифрованным и незашифрованным соединением, или если пользователь меняет текущую комнату. Однако, поскольку `createConnection` является функцией, вы не можете проверить, изменилась ли информация, которую она читает, *фактически*. Чтобы решить эту проблему, вместо передачи `createConnection` из компонента `App`, передайте необработанные значения `roomId` и `isEncrypted`: +Вы не можете сделать то же самое с `createConnection`, потому что он *должен* быть реактивным. Вы *хотите*, чтобы Effect перезапускался, если пользователь переключается между зашифрованным и незашифрованным подключением, или если пользователь переключает текущую комнату. Однако, поскольку `createConnection` является функцией, вы не можете проверить, *действительно* ли изменилась информация, которую она считывает, или нет. Чтобы решить эту проблему, вместо передачи `createConnection` из компонента `App`, передайте необработанные значения `roomId` и `isEncrypted`: ```js {2-3} ``` -Теперь вы можете переместить функцию `createConnection` *внутрь* эффекта вместо передачи ее из `App`: +Теперь вы можете переместить функцию `createConnection` *внутрь* Effect, а не передавать ее из `App`: ```js {1-4,6,10-20} import { @@ -2087,19 +2092,19 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // ... ``` -После этих двух изменений ваш эффект больше не зависит от значений функций: +После этих двух изменений ваш Effect больше не зависит от каких-либо значений функций: ```js {1,8,10,21} -export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Реактивные значения - const onReceiveMessage = useEffectEvent(onMessage); // Не реактивное +export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reactive values + const onReceiveMessage = useEffectEvent(onMessage); // Not reactive useEffect(() => { function createConnection() { const options = { serverUrl: 'https://localhost:1234', - roomId: roomId // Чтение реактивного значения + roomId: roomId // Reading a reactive value }; - if (isEncrypted) { // Чтение реактивного значения + if (isEncrypted) { // Reading a reactive value return createEncryptedConnection(options); } else { return createUnencryptedConnection(options); @@ -2110,10 +2115,10 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Реа connection.on('message', (msg) => onReceiveMessage(msg)); connection.connect(); return () => connection.disconnect(); - }, [roomId, isEncrypted]); // ✅ Все зависимости объявлены + }, [roomId, isEncrypted]); // ✅ All dependencies declared ``` -В результате чат переподключается только тогда, когда изменяется что-то значимое (`roomId` или `isEncrypted`): +В результате чат переподключается только тогда, когда что-то значимое (`roomId` или `isEncrypted`) меняется: @@ -2219,14 +2224,19 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { return

Welcome to the {roomId} room!

; } -```javascript src/chat.js +``` + +
+ +``` +```js src/chat.js export function createEncryptedConnection({ serverUrl, roomId }) { - // Реальная реализация будет подключаться к серверу + // Реальная реализация на самом деле подключится к серверу if (typeof serverUrl !== 'string') { - throw Error('Ожидался serverUrl типа string. Получено: ' + serverUrl); + throw Error('Ожидалось, что serverUrl будет строкой. Получено: ' + serverUrl); } if (typeof roomId !== 'string') { - throw Error('Ожидался roomId типа string. Получено: ' + roomId); + throw Error('Ожидалось, что roomId будет строкой. Получено: ' + roomId); } let intervalId; let messageCallback; @@ -2247,7 +2257,7 @@ export function createEncryptedConnection({ serverUrl, roomId }) { disconnect() { clearInterval(intervalId); messageCallback = null; - console.log('❌ 🔐 Отключено от комнаты "' + roomId + '" (зашифровано)'); + console.log('❌ 🔐 Отключение от комнаты "' + roomId + '" (зашифровано)'); }, on(event, callback) { if (messageCallback) { @@ -2262,12 +2272,12 @@ export function createEncryptedConnection({ serverUrl, roomId }) { } export function createUnencryptedConnection({ serverUrl, roomId }) { - // Реальная реализация будет подключаться к серверу + // Реальная реализация на самом деле подключится к серверу if (typeof serverUrl !== 'string') { - throw Error('Ожидался serverUrl типа string. Получено: ' + serverUrl); + throw Error('Ожидалось, что serverUrl будет строкой. Получено: ' + serverUrl); } if (typeof roomId !== 'string') { - throw Error('Ожидался roomId типа string. Получено: ' + roomId); + throw Error('Ожидалось, что roomId будет строкой. Получено: ' + roomId); } let intervalId; let messageCallback; @@ -2288,7 +2298,7 @@ export function createUnencryptedConnection({ serverUrl, roomId }) { disconnect() { clearInterval(intervalId); messageCallback = null; - console.log('❌ Отключено от комнаты "' + roomId + '" (незашифровано)'); + console.log('❌ Отключение от комнаты "' + roomId + '" (незашифровано)'); }, on(event, callback) { if (messageCallback) { @@ -2303,7 +2313,7 @@ export function createUnencryptedConnection({ serverUrl, roomId }) { } ``` -```javascript src/notifications.js +```js src/notifications.js import Toastify from 'toastify-js'; import 'toastify-js/src/toastify.css'; @@ -2329,4 +2339,5 @@ label, button { display: block; margin-bottom: 5px; }
-
\ No newline at end of file + +``` \ No newline at end of file From 03b80881f39df6baffa724fb7514091e46c773ed Mon Sep 17 00:00:00 2001 From: "translate-react-bot[bot]" <251169733+translate-react-bot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 16:17:50 +0000 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20translate=20`removing-effect-depend?= =?UTF-8?q?encies.md`=20to=20=D0=A0=D1=83=D1=81=D1=81=D0=BA=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../learn/removing-effect-dependencies.md | 394 +++++++++--------- 1 file changed, 192 insertions(+), 202 deletions(-) diff --git a/src/content/learn/removing-effect-dependencies.md b/src/content/learn/removing-effect-dependencies.md index d962275f42..665fdfbd27 100644 --- a/src/content/learn/removing-effect-dependencies.md +++ b/src/content/learn/removing-effect-dependencies.md @@ -1,10 +1,9 @@ --- -title: 'Удаление зависимостей эффектов' +title: 'Удаление зависимостей эффекта' --- - -Когда вы пишете Effect, линтер проверит, что вы включили каждое реактивное значение (например, пропсы и состояние), которое Effect считывает, в список зависимостей вашего Effect. Это гарантирует, что ваш Effect остается синхронизированным с последними пропсами и состоянием вашего компонента. Необязательные зависимости могут привести к тому, что ваш Effect будет запускаться слишком часто или даже создаст бесконечный цикл. Следуйте этому руководству, чтобы просмотреть и удалить ненужные зависимости из ваших Effects. +Когда вы пишете Effect, линтер проверяет, включили ли вы все реактивные значения (например, пропсы и состояние), которые Effect читает, в список зависимостей вашего Effect. Это гарантирует, что ваш Effect остается синхронизированным с последними пропсами и состоянием вашего компонента. Ненужные зависимости могут привести к тому, что ваш Effect будет выполняться слишком часто или даже создаст бесконечный цикл. Следуйте этому руководству, чтобы просмотреть и удалить ненужные зависимости из ваших Effects. @@ -12,15 +11,15 @@ title: 'Удаление зависимостей эффектов' - Как исправить бесконечные циклы зависимостей Effect - Что делать, когда вы хотите удалить зависимость -- Как прочитать значение из вашего Effect, не «реагируя» на него -- Как и почему следует избегать зависимостей от объектов и функций +- Как читать значение из вашего Effect, не "реагируя" на него +- Как и почему следует избегать объектных и функциональных зависимостей - Почему подавление линтера зависимостей опасно и что делать вместо этого ## Зависимости должны соответствовать коду {/*dependencies-should-match-the-code*/} -Когда вы пишете Effect, вы сначала указываете, как [запустить и остановить](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) все, что вы хотите, чтобы ваш Effect делал: +Когда вы пишете Effect, вы сначала определяете, как [начать и остановить](/learn/lifecycle-of-reactive-effects#the-lifecycle-of-an-effect) выполнение того, что вы хотите, чтобы ваш Effect делал: ```js {5-7} const serverUrl = 'https://localhost:1234'; @@ -109,7 +108,7 @@ function ChatRoom({ roomId }) { } ``` -[Effects «реагируют» на реактивные значения.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Поскольку `roomId` является реактивным значением (оно может измениться из-за повторного рендеринга), линтер проверяет, что вы указали его в качестве зависимости. Если `roomId` получает другое значение, React повторно синхронизирует ваш Effect. Это гарантирует, что чат остается подключенным к выбранной комнате и «реагирует» на раскрывающийся список: +[Effects "реагируют" на реактивные значения.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Поскольку `roomId` является реактивным значением (оно может изменяться из-за повторного рендеринга), линтер проверяет, указали ли вы его в качестве зависимости. Если `roomId` получит другое значение, React повторно синхронизирует ваш Effect. Это гарантирует, что чат останется подключенным к выбранной комнате и будет "реагировать" на выпадающий список: @@ -173,22 +172,22 @@ button { margin-left: 10px; } ### Чтобы удалить зависимость, докажите, что она не является зависимостью {/*to-remove-a-dependency-prove-that-its-not-a-dependency*/} -Обратите внимание, что вы не можете «выбрать» зависимости вашего Effect. Каждое реактивное значение, используемое кодом вашего Effect, должно быть объявлено в списке ваших зависимостей. Список зависимостей определяется окружающим кодом: +Обратите внимание, что вы не можете "выбирать" зависимости вашего Effect. Каждое реактивное значение, используемое кодом вашего Effect, должно быть объявлено в вашем списке зависимостей. Список зависимостей определяется окружающим кодом: ```js [[2, 3, "roomId"], [2, 5, "roomId"], [2, 8, "roomId"]] const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { // Это реактивное значение useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Этот Effect считывает это реактивное значение + const connection = createConnection(serverUrl, roomId); // Этот Effect читает это реактивное значение connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ Поэтому вы должны указать это реактивное значение в качестве зависимости вашего Effect + }, [roomId]); // ✅ Поэтому вы должны указать это реактивное значение как зависимость вашего Effect // ... } ``` -[Реактивные значения](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) включают пропсы и все переменные и функции, объявленные непосредственно внутри вашего компонента. Поскольку `roomId` является реактивным значением, вы не можете удалить его из списка зависимостей. Линтер этого не позволит: +[Реактивные значения](/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive) включают пропсы и все переменные и функции, объявленные непосредственно внутри вашего компонента. Поскольку `roomId` является реактивным значением, вы не можете удалить его из списка зависимостей. Линтер не позволит этого: ```js {8} const serverUrl = 'https://localhost:1234'; @@ -198,14 +197,14 @@ function ChatRoom({ roomId }) { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); - }, []); // 🔴 React Hook useEffect has a missing dependency: 'roomId' + }, []); // 🔴 React Hook useEffect имеет отсутствующую зависимость: 'roomId' // ... } ``` И линтер будет прав! Поскольку `roomId` может меняться со временем, это приведет к ошибке в вашем коде. -**Чтобы удалить зависимость, «докажите» линтеру, что она *не нуждается* в том, чтобы быть зависимостью.** Например, вы можете переместить `roomId` за пределы вашего компонента, чтобы доказать, что он не является реактивным и не изменится при повторном рендеринге: +**Чтобы удалить зависимость, "докажите" линтеру, что она *не нуждается* в том, чтобы быть зависимостью.** Например, вы можете вынести `roomId` из вашего компонента, чтобы доказать, что он не реактивный и не изменится при повторных рендерингах: ```js {2,9} const serverUrl = 'https://localhost:1234'; @@ -221,7 +220,7 @@ function ChatRoom() { } ``` -Теперь, когда `roomId` не является реактивным значением (и не может измениться при повторном рендеринге), ему не нужно быть зависимостью: +Теперь, поскольку `roomId` не является реактивным значением (и не может измениться при повторном рендеринге), оно не нуждается в том, чтобы быть зависимостью: @@ -263,23 +262,23 @@ button { margin-left: 10px; } -Вот почему теперь вы можете указать [пустой (`[]`) список зависимостей.](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) Ваш Effect *действительно не* зависит больше ни от какого реактивного значения, поэтому ему *действительно не нужно* перезапускаться, когда изменяются какие-либо пропсы или состояние компонента. +Вот почему вы теперь можете указать [пустой (`[]`) список зависимостей.](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) Ваш Effect *действительно* не зависит ни от одного реактивного значения, поэтому он *действительно* не нуждается в повторном запуске при изменении любого из пропсов или состояния компонента. -### Чтобы изменить зависимости, сначала измените код {/*to-change-the-dependencies-change-the-code*/} +### Чтобы изменить зависимости, измените код {/*to-change-the-dependencies-change-the-code*/} -Вы могли заметить закономерность в вашем рабочем процессе: +Вы могли заметить закономерность в своем рабочем процессе: -1. Сначала вы **изменяете код** вашего Effect или то, как объявляются ваши реактивные значения. -2. Затем вы следуете линтеру и настраиваете зависимости, чтобы **соответствовать коду, который вы изменили.** -3. Если вы не довольны списком зависимостей, вы **возвращаетесь к первому шагу** (и снова меняете код). +1. Сначала вы **изменяете код** вашего Effect или способ объявления ваших реактивных значений. +2. Затем вы следуете за линтером и корректируете зависимости, чтобы они **соответствовали измененному вами коду.** +3. Если вас не устраивает список зависимостей, вы **возвращаетесь к первому шагу** (и снова изменяете код). -Последняя часть важна. **Если вы хотите изменить зависимости, сначала измените окружающий код.** Вы можете думать о списке зависимостей как [о списке всех реактивных значений, используемых кодом вашего Effect.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) Вы не *выбираете*, что поместить в этот список. Список *описывает* ваш код. Чтобы изменить список зависимостей, измените код. +Последняя часть важна. **Если вы хотите изменить зависимости, сначала измените окружающий код.** Вы можете рассматривать список зависимостей как [список всех реактивных значений, используемых кодом вашего Effect.](/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency) Вы не *выбираете*, что поместить в этот список. Список *описывает* ваш код. Чтобы изменить список зависимостей, измените код. -Это может показаться решением уравнения. Вы можете начать с цели (например, удалить зависимость), и вам нужно «найти» код, соответствующий этой цели. Не всем нравится решать уравнения, и то же самое можно сказать о написании Effects! К счастью, ниже приведен список распространенных рецептов, которые вы можете попробовать. +Это может показаться решением уравнения. Вы можете начать с цели (например, удалить зависимость), и вам нужно будет "найти" код, соответствующий этой цели. Не всем нравится решать уравнения, и то же самое можно сказать о написании Effects! К счастью, ниже приведен список распространенных рецептов, которые вы можете попробовать. -Если у вас есть существующая кодовая база, у вас могут быть некоторые Effects, которые подавляют линтер, например: +Если у вас есть существующая кодовая база, у вас могут быть некоторые Effects, которые подавляют линтер следующим образом: ```js {3-4} useEffect(() => { @@ -289,9 +288,9 @@ useEffect(() => { }, []); ``` -**Когда зависимости не соответствуют коду, существует очень высокий риск внесения ошибок.** Подавляя линтер, вы «лжете» React о значениях, от которых зависит ваш Effect. +**Когда зависимости не соответствуют коду, существует очень высокий риск внесения ошибок.** Подавляя линтер, вы "лжете" React о значениях, от которых зависит ваш Effect. -Вместо этого используйте методы, описанные ниже. +Вместо этого используйте приведенные ниже методы. @@ -348,32 +347,31 @@ button { margin: 10px; } -Предположим, вы хотите запустить Effect «только при монтировании». Вы прочитали, что [пустые (`[]`) зависимости](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) делают это, поэтому вы решили игнорировать линтер и принудительно указали `[]` в качестве зависимостей. +Допустим, вы хотели запустить Effect "только при монтировании". Вы прочитали, что [пустые зависимости (`[]`)](/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means) делают это, поэтому вы решили проигнорировать линтер и принудительно указали `[]` в качестве зависимостей. -Этот счетчик должен был увеличиваться каждую секунду на величину, настраиваемую с помощью двух кнопок. Однако, поскольку вы «солгали» React, что этот Effect ни от чего не зависит, React навсегда продолжает использовать функцию `onTick` из начального рендеринга. [Во время этого рендеринга,](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` было `0`, а `increment` было `1`. Вот почему `onTick` из этого рендеринга всегда вызывает `setCount(0 + 1)` каждую секунду, и вы всегда видите `1`. Ошибки, подобные этой, труднее исправить, когда они разбросаны по нескольким компонентам. +Этот счетчик должен был увеличиваться каждую секунду на величину, настраиваемую двумя кнопками. Однако, поскольку вы "солгал" React, что этот Effect ни от чего не зависит, React навсегда сохраняет использование функции `onTick` из первоначального рендеринга. [Во время этого рендеринга,](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` был `0`, а `increment` был `1`. Вот почему `onTick` из этого рендеринга каждую секунду вызывает `setCount(0 + 1)`, и вы всегда видите `1`. Ошибки, подобные этой, труднее исправить, когда они распределены по нескольким компонентам. -Всегда есть лучшее решение, чем игнорирование линтера! Чтобы исправить этот код, вам нужно добавить `onTick` в список зависимостей. (Чтобы убедиться, что интервал настроен только один раз, [сделайте `onTick` событием Effect.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)) +Всегда есть лучшее решение, чем игнорировать линтер! Чтобы исправить этот код, вам нужно добавить `onTick` в список зависимостей. (Чтобы гарантировать, что интервал будет установлен только один раз, [сделайте `onTick` событием Effect.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)) -**Мы рекомендуем относиться к ошибке линта зависимостей как к ошибке компиляции. Если вы не подавите ее, вы никогда не увидите таких ошибок.** Остальная часть этой страницы документирует альтернативы для этого и других случаев. +**Мы рекомендуем относиться к ошибке линтера зависимостей как к ошибке компиляции. Если вы не будете ее подавлять, вы никогда не увидите подобных ошибок.** Остальная часть этой страницы документирует альтернативы для этого и других случаев. - ## Удаление ненужных зависимостей {/*removing-unnecessary-dependencies*/} -Каждый раз, когда вы настраиваете зависимости Effect в соответствии с кодом, смотрите на список зависимостей. Имеет ли смысл, чтобы Effect перезапускался при изменении любой из этих зависимостей? Иногда ответ — «нет»: +Каждый раз, когда вы корректируете зависимости Effect, чтобы они отражали код, взгляните на список зависимостей. Имеет ли смысл повторное выполнение Effect при изменении любой из этих зависимостей? Иногда ответ — «нет»: -* Возможно, вы захотите повторно выполнить *разные части* вашего Effect при разных условиях. -* Возможно, вы захотите только прочитать *последнее значение* какой-либо зависимости, а не «реагировать» на ее изменения. +* Вы можете захотеть повторно выполнить *различные части* вашего Effect при разных условиях. +* Вы можете захотеть только прочитать *последнее значение* некоторой зависимости, вместо того чтобы «реагировать» на её изменения. * Зависимость может меняться слишком часто *непреднамеренно*, потому что это объект или функция. -Чтобы найти правильное решение, вам нужно будет ответить на несколько вопросов о вашем Effect. Давайте рассмотрим их. +Чтобы найти правильное решение, вам нужно будет ответить на несколько вопросов о вашем Effect. Давайте пройдемся по ним. -### Должен ли этот код переместиться в обработчик событий? {/*should-this-code-move-to-an-event-handler*/} +### Следует ли переместить этот код в обработчик события? {/*should-this-code-move-to-an-event-handler*/} -Первое, о чем вы должны подумать, — это должен ли этот код вообще быть Effect. +Первое, о чем следует подумать, — это должен ли этот код вообще быть Effect. -Представьте себе форму. При отправке вы устанавливаете переменную состояния `submitted` в `true`. Вам нужно отправить POST-запрос и показать уведомление. Вы поместили эту логику внутрь Effect, который «реагирует» на то, что `submitted` равно `true`: +Представьте форму. При отправке вы устанавливаете переменную состояния `submitted` в `true`. Вам нужно отправить POST-запрос и показать уведомление. Вы поместили эту логику внутрь Effect, который «реагирует» на `submitted` равное `true`: ```js {6-8} function Form() { @@ -381,7 +379,7 @@ function Form() { useEffect(() => { if (submitted) { - // 🔴 Избегайте: логика, специфичная для события, внутри Effect + // 🔴 Избегайте: Логика, специфичная для события, внутри Effect post('/api/register'); showNotification('Successfully registered!'); } @@ -395,7 +393,7 @@ function Form() { } ``` -Позже вы хотите стилизовать сообщение уведомления в соответствии с текущей темой, поэтому вы читаете текущую тему. Поскольку `theme` объявлена в теле компонента, это реактивное значение, поэтому вы добавляете его в качестве зависимости: +Позже вы хотите стилизовать сообщение уведомления в соответствии с текущей темой, поэтому вы читаете текущую тему. Поскольку `theme` объявлена в теле компонента, это реактивное значение, поэтому вы добавляете ее в зависимости: ```js {3,9,11} function Form() { @@ -404,11 +402,11 @@ function Form() { useEffect(() => { if (submitted) { - // 🔴 Избегайте: логика, специфичная для события, внутри Effect + // 🔴 Избегайте: Логика, специфичная для события, внутри Effect post('/api/register'); showNotification('Successfully registered!', theme); } - }, [submitted, theme]); // ✅ Объявлены все зависимости + }, [submitted, theme]); // ✅ Все зависимости объявлены function handleSubmit() { setSubmitted(true); @@ -418,16 +416,16 @@ function Form() { } ``` -Сделав это, вы внесли ошибку. Представьте, что вы сначала отправляете форму, а затем переключаетесь между темной и светлой темами. `theme` изменится, Effect перезапустится, и поэтому он снова отобразит то же уведомление! +Сделав это, вы внесли ошибку. Представьте, что вы сначала отправляете форму, а затем переключаетесь между темными и светлыми темами. `theme` изменится, Effect повторно выполнится, и поэтому он снова отобразит то же уведомление! -**Проблема здесь в том, что это вообще не должно быть Effect.** Вы хотите отправить этот POST-запрос и показать уведомление в ответ на *отправку формы*, что является конкретным взаимодействием. Чтобы запустить какой-то код в ответ на конкретное взаимодействие, поместите эту логику непосредственно в соответствующий обработчик событий: +**Проблема здесь в том, что это вообще не должно быть Effect.** Вы хотите отправить этот POST-запрос и показать уведомление в ответ на *отправку формы*, что является конкретным взаимодействием. Чтобы выполнить какой-либо код в ответ на конкретное взаимодействие, поместите эту логику непосредственно в соответствующий обработчик события: ```js {6-7} function Form() { const theme = useContext(ThemeContext); function handleSubmit() { - // ✅ Хорошо: логика, специфичная для события, вызывается из обработчиков событий + // ✅ Хорошо: Логика, специфичная для события, вызывается из обработчиков событий post('/api/register'); showNotification('Successfully registered!', theme); } @@ -436,13 +434,13 @@ function Form() { } ``` -Теперь, когда код находится в обработчике событий, он не является реактивным, поэтому он будет выполняться только тогда, когда пользователь отправляет форму. Узнайте больше о [выборе между обработчиками событий и Effects](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) и [о том, как удалить ненужные Effects.](/learn/you-might-not-need-an-effect) +Теперь, когда код находится в обработчике события, он не является реактивным — он будет выполняться только тогда, когда пользователь отправляет форму. Читайте больше о [выборе между обработчиками событий и Effects](/learn/separating-events-from-effects#reactive-values-and-reactive-logic) и о [том, как удалить ненужные Effects.](/learn/you-might-not-need-an-effect) -### Выполняет ли ваш Effect несколько несвязанных действий? {/*is-your-effect-doing-several-unrelated-things*/} +### Ваш Effect выполняет несколько несвязанных действий? {/*is-your-effect-doing-several-unrelated-things*/} Следующий вопрос, который вы должны себе задать, — выполняет ли ваш Effect несколько несвязанных действий. -Представьте, что вы создаете форму доставки, где пользователю нужно выбрать свой город и область. Вы получаете список `cities` с сервера в соответствии с выбранной `country`, чтобы отобразить их в раскрывающемся списке: +Представьте, что вы создаете форму доставки, где пользователю нужно выбрать город и район. Вы получаете список `cities` с сервера в соответствии с выбранной `country`, чтобы показать их в выпадающем списке: ```js function ShippingForm({ country }) { @@ -461,14 +459,14 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country]); // ✅ Объявлены все зависимости + }, [country]); // ✅ Все зависимости объявлены // ... ``` -Это хороший пример [получения данных в Effect.](/learn/you-might-not-need-an-effect#fetching-data) Вы синхронизируете состояние `cities` с сетью в соответствии с пропсом `country`. Вы не можете сделать это в обработчике событий, потому что вам нужно получить данные, как только отобразится `ShippingForm`, и всякий раз, когда изменяется `country` (независимо от того, какое взаимодействие вызывает это). +Это хороший пример [получения данных в Effect.](/learn/you-might-not-need-an-effect#fetching-data) Вы синхронизируете состояние `cities` с сетью в соответствии с пропом `country`. Вы не можете сделать это в обработчике события, потому что вам нужно получать данные, как только `ShippingForm` отобразится, и всякий раз, когда `country` изменится (независимо от того, какое взаимодействие это вызвало). -Теперь давайте предположим, что вы добавляете второй раскрывающийся список для областей города, который должен получать `areas` для выбранного в данный момент `city`. Вы можете начать с добавления второго вызова `fetch` для списка областей внутри того же Effect: +Теперь предположим, вы добавляете второй выпадающий список для районов города, который должен получать `areas` для текущего выбранного `city`. Вы можете начать с добавления второго вызова `fetch` для списка районов в тот же Effect: ```js {15-24,28} function ShippingForm({ country }) { @@ -485,7 +483,7 @@ function ShippingForm({ country }) { setCities(json); } }); - // 🔴 Избегайте: один Effect синхронизирует два независимых процесса + // 🔴 Избегайте: Единый Effect синхронизирует два независимых процесса if (city) { fetch(`/api/areas?city=${city}`) .then(response => response.json()) @@ -498,19 +496,19 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country, city]); // ✅ Объявлены все зависимости + }, [country, city]); // ✅ Все зависимости объявлены // ... ``` -Однако, поскольку Effect теперь использует переменную состояния `city`, вам пришлось добавить `city` в список зависимостей. Это, в свою очередь, привело к проблеме: когда пользователь выбирает другой город, Effect перезапустится и вызовет `fetchCities(country)`. В результате вы будете ненужно повторно получать список городов много раз. +Однако, поскольку Effect теперь использует переменную состояния `city`, вам пришлось добавить `city` в список зависимостей. Это, в свою очередь, привело к проблеме: когда пользователь выбирает другой город, Effect повторно выполнится и вызовет `fetchCities(country)`. В результате вы будете ненужно много раз перезапрашивать список городов. -**Проблема с этим кодом в том, что вы синхронизируете две разные несвязанные вещи:** +**Проблема этого кода в том, что вы синхронизируете две разные несвязанные вещи:** -1. Вы хотите синхронизировать состояние `cities` с сетью на основе пропса `country`. +1. Вы хотите синхронизировать состояние `cities` с сетью на основе пропа `country`. 1. Вы хотите синхронизировать состояние `areas` с сетью на основе состояния `city`. -Разделите логику на два Effects, каждый из которых реагирует на пропс, который ему необходимо синхронизировать: +Разделите логику на два Effect, каждый из которых реагирует на проп, с которым он должен синхронизироваться: ```js {19-33} function ShippingForm({ country }) { @@ -527,7 +525,7 @@ function ShippingForm({ country }) { return () => { ignore = true; }; - }, [country]); // ✅ Объявлены все зависимости + }, [country]); // ✅ Все зависимости объявлены const [city, setCity] = useState(null); const [areas, setAreas] = useState(null); @@ -545,18 +543,18 @@ function ShippingForm({ country }) { ignore = true; }; } - }, [city]); // ✅ Объявлены все зависимости + }, [city]); // ✅ Все зависимости объявлены // ... ``` -Теперь первый Effect перезапускается только в том случае, если изменяется `country`, в то время как второй Effect перезапускается, когда изменяется `city`. Вы разделили их по назначению: два разных действия синхронизируются двумя отдельными Effects. Два отдельных Effects имеют два отдельных списка зависимостей, поэтому они не будут непреднамеренно запускать друг друга. +Теперь первый Effect повторно выполняется только в том случае, если `country` изменится, а второй Effect повторно выполняется при изменении `city`. Вы разделили их по назначению: две разные вещи синхронизируются двумя отдельными Effect. Два отдельных Effect имеют два отдельных списка зависимостей, поэтому они не будут непреднамеренно вызывать друг друга. -Окончательный код длиннее исходного, но разделение этих Effects все равно правильно. [Каждый Effect должен представлять собой независимый процесс синхронизации.](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) В этом примере удаление одного Effect не нарушает логику другого Effect. Это означает, что они *синхронизируют разные вещи*, и их хорошо разделить. Если вы беспокоитесь о дублировании, вы можете улучшить этот код, [извлекая повторяющуюся логику в пользовательский Hook.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks) +Итоговый код длиннее исходного, но разделение этих Effect все равно является правильным. [Каждый Effect должен представлять собой независимый процесс синхронизации.](/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process) В этом примере удаление одного Effect не нарушает логику другого Effect. Это означает, что они *синхронизируют разные вещи*, и их хорошо разделить. Если вас беспокоит дублирование, вы можете улучшить этот код, [извлекши повторяющуюся логику в пользовательский хук.](/learn/reusing-logic-with-custom-hooks#when-to-use-custom-hooks) -### Вы читаете какое-то состояние, чтобы вычислить следующее состояние? {/*are-you-reading-some-state-to-calculate-the-next-state*/} +### Вы читаете некоторое состояние для вычисления следующего состояния? {/*are-you-reading-some-state-to-calculate-the-next-state*/} -Этот Effect обновляет переменную состояния `messages` с помощью вновь созданного массива каждый раз, когда приходит новое сообщение: +Этот Effect обновляет переменную состояния `messages` новым созданным массивом каждый раз, когда приходит новое сообщение: ```js {2,6-8} function ChatRoom({ roomId }) { @@ -570,7 +568,7 @@ function ChatRoom({ roomId }) { // ... ``` -Он использует переменную `messages`, чтобы [создать новый массив](/learn/updating-arrays-in-state), начиная со всех существующих сообщений и добавляя новое сообщение в конец. Однако, поскольку `messages` — это реактивное значение, считываемое Effect, оно должно быть зависимостью: +Он использует переменную `messages` для [создания нового массива](/learn/updating-arrays-in-state), начиная со всех существующих сообщений и добавляя новое сообщение в конец. Однако, поскольку `messages` является реактивным значением, считываемым Effect, оно должно быть зависимостью: ```js {7,10} function ChatRoom({ roomId }) { @@ -582,13 +580,13 @@ function ChatRoom({ roomId }) { setMessages([...messages, receivedMessage]); }); return () => connection.disconnect(); - }, [roomId, messages]); // ✅ Объявлены все зависимости + }, [roomId, messages]); // ✅ Все зависимости объявлены // ... ``` -И добавление `messages` в качестве зависимости создает проблему. +И добавление `messages` в зависимости создает проблему. -Каждый раз, когда вы получаете сообщение, `setMessages()` заставляет компонент перерендериваться с новым массивом `messages`, который включает полученное сообщение. Однако, поскольку этот Effect теперь зависит от `messages`, это *также* повторно синхронизирует Effect. Таким образом, каждое новое сообщение заставит чат переподключиться. Пользователю это не понравится! +Каждый раз, когда вы получаете сообщение, `setMessages()` вызывает повторный рендеринг компонента с новым массивом `messages`, который включает полученное сообщение. Однако, поскольку этот Effect теперь зависит от `messages`, это *также* повторно синхронизирует Effect. Таким образом, каждое новое сообщение приведет к повторному подключению чата. Пользователю это не понравится! Чтобы исправить проблему, не читайте `messages` внутри Effect. Вместо этого передайте [функцию обновления](/reference/react/useState#updating-state-based-on-the-previous-state) в `setMessages`: @@ -602,22 +600,21 @@ function ChatRoom({ roomId }) { setMessages(msgs => [...msgs, receivedMessage]); }); return () => connection.disconnect(); - }, [roomId]); // ✅ Объявлены все зависимости + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -**Обратите внимание, как ваш Effect вообще не читает переменную `messages`.** Вам нужно только передать функцию обновления, например `msgs => [...msgs, receivedMessage]`. React [помещает вашу функцию обновления в очередь](/learn/queueing-a-series-of-state-updates) и предоставит ей аргумент `msgs` во время следующего рендеринга. Вот почему сам Effect больше не должен зависеть от `messages`. В результате этого исправления получение сообщения чата больше не будет приводить к повторному подключению чата. - +**Обратите внимание, что ваш Effect теперь вообще не читает переменную `messages`.** Вам нужно только передать функцию обновления, такую как `msgs => [...msgs, receivedMessage]`. React [помещает вашу функцию обновления в очередь](/learn/queueing-a-series-of-state-updates) и предоставит ей аргумент `msgs` во время следующего рендеринга. Вот почему сам Effect больше не должен зависеть от `messages`. В результате этого исправления получение сообщения чата больше не приведет к повторному подключению чата. -### Хотите прочитать значение, не «реагируя» на его изменения? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/} +### Вы хотите прочитать значение, не «реагируя» на его изменения? {/*do-you-want-to-read-a-value-without-reacting-to-its-changes*/} -Этот раздел описывает **экспериментальный API, который ещё не был выпущен** в стабильной версии React. +Этот раздел описывает **экспериментальный API, который еще не был выпущен** в стабильной версии React. -Предположим, вы хотите воспроизводить звук, когда пользователь получает новое сообщение, если только `isMuted` не равно `true`: +Предположим, вы хотите воспроизвести звук при получении пользователем нового сообщения, если только `isMuted` не равно `true`: ```js {3,10-12} function ChatRoom({ roomId }) { @@ -636,7 +633,7 @@ function ChatRoom({ roomId }) { // ... ``` -Поскольку ваш Effect теперь использует `isMuted` в своем коде, вам нужно добавить его в зависимости: +Поскольку ваш Effect теперь использует `isMuted` в своем коде, вам придется добавить его в зависимости: ```js {10,15} function ChatRoom({ roomId }) { @@ -653,13 +650,13 @@ function ChatRoom({ roomId }) { } }); return () => connection.disconnect(); - }, [roomId, isMuted]); // ✅ Объявлены все зависимости + }, [roomId, isMuted]); // ✅ Все зависимости объявлены // ... ``` -Проблема в том, что каждый раз, когда `isMuted` меняется (например, когда пользователь нажимает переключатель «Muted»), Effect будет повторно синхронизироваться и переподключаться к чату. Это не желаемый пользовательский опыт! (В этом примере даже отключение линтера не сработает — если вы это сделаете, `isMuted` «застрянет» со своим старым значением.) +Проблема в том, что каждый раз, когда `isMuted` изменяется (например, когда пользователь нажимает переключатель «Отключить звук»), Effect будет повторно синхронизироваться и переподключаться к чату. Это нежелательный пользовательский опыт! (В этом примере даже отключение линтера не помогло бы — если вы это сделаете, `isMuted` «застрянет» со своим старым значением.) -Чтобы решить эту проблему, вам нужно извлечь логику, которая не должна быть реактивной, из Effect. Вы не хотите, чтобы этот Effect «реагировал» на изменения в `isMuted`. [Переместите эту нереактивную часть логики в Effect Event:](/learn/separating-events-from-effects#declaring-an-effect-event) +Чтобы решить эту проблему, вам нужно извлечь логику, которая не должна быть реактивной, из Effect. Вы не хотите, чтобы этот Effect «реагировал» на изменения `isMuted`. [Переместите эту нереактивную часть логики в событие Effect:](/learn/separating-events-from-effects#declaring-an-effect-event) ```js {1,7-12,18,21} import { useState, useEffect, useEffectEvent } from 'react'; @@ -682,15 +679,15 @@ function ChatRoom({ roomId }) { onMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId]); // ✅ Объявлены все зависимости + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -Effect Events позволяют разделить Effect на реактивные части (которые должны «реагировать» на реактивные значения, такие как `roomId`, и их изменения) и нереактивные части (которые только читают свои последние значения, например, `onMessage` читает `isMuted`). **Теперь, когда вы читаете `isMuted` внутри Effect Event, ему не нужно быть зависимостью вашего Effect.** В результате чат не будет переподключаться при включении и выключении настройки «Muted», что решает исходную проблему! +События Effect позволяют разделить Effect на реактивные части (которые должны «реагировать» на реактивные значения, такие как `roomId`, и их изменения) и нереактивные части (которые только считывают свои последние значения, такие как `onMessage` считывает `isMuted`). **Теперь, когда вы считываете `isMuted` внутри события Effect, оно не должно быть зависимостью вашего Effect.** В результате чат не будет переподключаться при включении и выключении настройки «Отключить звук», решая исходную проблему! -#### Оборачивание обработчика событий из пропсов {/*wrapping-an-event-handler-from-the-props*/} +#### Обертывание обработчика события из пропсов {/*wrapping-an-event-handler-from-the-props*/} -Вы можете столкнуться с аналогичной проблемой, когда ваш компонент получает обработчик событий в качестве пропа: +Вы можете столкнуться с похожей проблемой, когда ваш компонент получает обработчик события в виде пропа: ```js {1,8,11} function ChatRoom({ roomId, onReceiveMessage }) { @@ -703,11 +700,11 @@ function ChatRoom({ roomId, onReceiveMessage }) { onReceiveMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId, onReceiveMessage]); // ✅ Объявлены все зависимости + }, [roomId, onReceiveMessage]); // ✅ Все зависимости объявлены // ... ``` -Предположим, что родительский компонент передаёт *другую* функцию `onReceiveMessage` при каждом рендере: +Предположим, родительский компонент передает *разную* функцию `onReceiveMessage` при каждом рендеринге: ```js {3-5} ``` -Поскольку `onReceiveMessage` является зависимостью, это приведёт к повторной синхронизации Effect после каждого повторного рендеринга родителя. Это заставит его переподключиться к чату. Чтобы решить эту проблему, оберните вызов в Effect Event: +Поскольку `onReceiveMessage` является зависимостью, это вызовет повторную синхронизацию Effect после каждого рендеринга родителя. Это приведет к повторному подключению к чату. Чтобы решить эту проблему, оберните вызов в событие Effect: ```js {4-6,12,15} function ChatRoom({ roomId, onReceiveMessage }) { @@ -735,17 +732,17 @@ function ChatRoom({ roomId, onReceiveMessage }) { onMessage(receivedMessage); }); return () => connection.disconnect(); - }, [roomId]); // ✅ Объявлены все зависимости + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -Effect Events не являются реактивными, поэтому вам не нужно указывать их в качестве зависимостей. В результате чат больше не будет переподключаться, даже если родительский компонент передаёт функцию, которая отличается при каждом повторном рендеринге. +События Effect не являются реактивными, поэтому вам не нужно указывать их как зависимости. В результате чат больше не будет переподключаться, даже если родительский компонент передает функцию, которая отличается при каждом повторном рендеринге. #### Разделение реактивного и нереактивного кода {/*separating-reactive-and-non-reactive-code*/} -В этом примере вы хотите регистрировать посещение каждый раз, когда `roomId` меняется. Вы хотите включить текущий `notificationCount` с каждой записью, но вы *не* хотите, чтобы изменение `notificationCount` вызывало событие регистрации. +В этом примере вы хотите регистрировать посещение каждый раз, когда `roomId` изменяется. Вы хотите включить текущее значение `notificationCount` в каждый лог, но вы *не* хотите, чтобы изменение `notificationCount` вызывало событие лога. -Решение состоит в том, чтобы снова разделить нереактивный код на Effect Event: +Решение снова заключается в том, чтобы выделить нереактивный код в событие Effect: ```js {2-4,7} function Chat({ roomId, notificationCount }) { @@ -755,17 +752,16 @@ function Chat({ roomId, notificationCount }) { useEffect(() => { onVisit(roomId); - }, [roomId]); // ✅ Объявлены все зависимости + }, [roomId]); // ✅ Все зависимости объявлены // ... } ``` -Вы хотите, чтобы ваша логика была реактивной по отношению к `roomId`, поэтому вы читаете `roomId` внутри вашего Effect. Однако вы не хотите, чтобы изменение `notificationCount` регистрировало дополнительное посещение, поэтому вы читаете `notificationCount` внутри Effect Event. [Узнайте больше о чтении последних пропсов и состояния из Effects с помощью Effect Events.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) - +Вы хотите, чтобы ваша логика была реактивной по отношению к `roomId`, поэтому вы считываете `roomId` внутри вашего Effect. Однако вы не хотите, чтобы изменение `notificationCount` регистрировало дополнительное посещение, поэтому вы считываете `notificationCount` внутри события Effect. [Узнайте больше о чтении последних пропсов и состояния из Effects с помощью событий Effect.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events) ### Непреднамеренное изменение реактивного значения? {/*does-some-reactive-value-change-unintentionally*/} -Иногда вы *действительно* хотите, чтобы ваш Effect «реагировал» на определенное значение, но это значение меняется чаще, чем вам хотелось бы, и может не отражать никаких фактических изменений с точки зрения пользователя. Например, предположим, что вы создаете объект `options` в теле вашего компонента, а затем считываете этот объект изнутри вашего Effect: +Иногда вы *хотите*, чтобы ваш эффект «реагировал» на определённое значение, но это значение меняется чаще, чем вам хотелось бы, и может не отражать реальных изменений с точки зрения пользователя. Например, предположим, вы создаёте объект `options` в теле вашего компонента, а затем читаете этот объект внутри вашего эффекта: ```js {3-6,9} function ChatRoom({ roomId }) { @@ -781,7 +777,7 @@ function ChatRoom({ roomId }) { // ... ``` -Этот объект объявлен в теле компонента, поэтому он является [реактивным значением.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Когда вы считываете реактивное значение, подобное этому, внутри Effect, вы объявляете его в качестве зависимости. Это гарантирует, что ваш Effect «реагирует» на его изменения: +Этот объект объявлен в теле компонента, поэтому он является [реактивным значением.](/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values) Когда вы читаете реактивное значение таким образом внутри эффекта, вы объявляете его как зависимость. Это гарантирует, что ваш эффект будет «реагировать» на его изменения: ```js {3,6} // ... @@ -789,11 +785,11 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [options]); // ✅ Объявлены все зависимости + }, [options]); // ✅ Все зависимости объявлены // ... ``` -Важно объявить его в качестве зависимости! Это гарантирует, например, что если `roomId` изменится, ваш Effect повторно подключится к чату с новыми `options`. Однако с приведенным выше кодом также есть проблема. Чтобы увидеть ее, попробуйте что-нибудь напечатать в поле ввода в песочнице ниже и посмотрите, что произойдет в консоли: +Важно объявить его как зависимость! Это гарантирует, например, что если `roomId` изменится, ваш эффект переподключится к чату с новыми `options`. Однако в приведённом выше коде есть и проблема. Чтобы увидеть её, попробуйте ввести текст в поле ввода в песочнице ниже и посмотрите, что происходит в консоли: @@ -806,7 +802,7 @@ const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); - // Временно отключите линтер, чтобы продемонстрировать проблему + // Временно отключим линтер, чтобы продемонстрировать проблему // eslint-disable-next-line react-hooks/exhaustive-deps const options = { serverUrl: serverUrl, @@ -851,7 +847,7 @@ export default function App() { ```js src/chat.js export function createConnection({ serverUrl, roomId }) { - // Реальная реализация на самом деле подключится к серверу + // Реальная реализация действительно подключится к серверу return { connect() { console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); @@ -870,11 +866,11 @@ button { margin-left: 10px; } -В приведенной выше песочнице поле ввода обновляет только переменную состояния `message`. С точки зрения пользователя это не должно влиять на подключение к чату. Однако каждый раз, когда вы обновляете `message`, ваш компонент перерендеривается. Когда ваш компонент перерендеривается, код внутри него запускается снова с нуля. +В песочнице выше поле ввода обновляет только переменную состояния `message`. С точки зрения пользователя, это не должно влиять на подключение к чату. Однако каждый раз, когда вы обновляете `message`, ваш компонент перерисовывается. Когда ваш компонент перерисовывается, код внутри него выполняется снова с самого начала. -Новый объект `options` создается с нуля при каждом перерендеринге компонента `ChatRoom`. React видит, что объект `options` — это *другой объект*, чем объект `options`, созданный во время последнего рендеринга. Вот почему он повторно синхронизирует ваш Effect (который зависит от `options`), и чат переподключается по мере ввода текста. +При каждом повторном рендеринге компонента `ChatRoom` заново создаётся новый объект `options`. React видит, что объект `options` — это *другой объект*, отличный от объекта `options`, созданного во время последнего рендеринга. Именно поэтому он повторно синхронизирует ваш эффект (который зависит от `options`), и чат переподключается по мере ввода текста. -**Эта проблема затрагивает только объекты и функции. В JavaScript каждый вновь созданный объект и функция считаются отличными от всех остальных. Не имеет значения, что содержимое внутри них может быть одинаковым!** +**Эта проблема затрагивает только объекты и функции. В JavaScript каждый вновь созданный объект и функция считаются отличными от всех остальных. Неважно, что их содержимое может быть одинаковым!** ```js {7-8} // Во время первого рендеринга @@ -887,13 +883,13 @@ const options2 = { serverUrl: 'https://localhost:1234', roomId: 'music' }; console.log(Object.is(options1, options2)); // false ``` -**Зависимости от объектов и функций могут приводить к повторной синхронизации вашего Effect чаще, чем вам нужно.** +**Зависимости объектов и функций могут заставлять ваш эффект повторно синхронизироваться чаще, чем вам нужно.** -Вот почему, когда это возможно, следует стараться избегать объектов и функций в качестве зависимостей вашего Effect. Вместо этого попробуйте переместить их за пределы компонента, внутрь Effect или извлечь из них примитивные значения. +Вот почему, когда это возможно, вам следует избегать объектов и функций в качестве зависимостей вашего эффекта. Вместо этого попробуйте вынести их за пределы компонента, внутрь эффекта или извлечь из них примитивные значения. -#### Переместите статические объекты и функции за пределы вашего компонента {/*move-static-objects-and-functions-outside-your-component*/} +#### Вынести статические объекты и функции за пределы компонента {/*move-static-objects-and-functions-outside-your-component*/} -Если объект не зависит от каких-либо пропсов и состояния, вы можете переместить этот объект за пределы вашего компонента: +Если объект не зависит от каких-либо пропсов и состояния, вы можете вынести этот объект за пределы вашего компонента: ```js {1-4,13} const options = { @@ -908,11 +904,11 @@ function ChatRoom() { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, []); // ✅ Объявлены все зависимости + }, []); // ✅ Все зависимости объявлены // ... ``` -Таким образом, вы *доказываете* линтеру, что он не реактивный. Он не может измениться в результате перерендеринга, поэтому он не должен быть зависимостью. Теперь перерендеринг `ChatRoom` не приведет к повторной синхронизации вашего Effect. +Таким образом, вы *доказываете* линтеру, что это не реактивное значение. Оно не может измениться в результате повторного рендеринга, поэтому ему не нужно быть зависимостью. Теперь повторный рендеринг `ChatRoom` не вызовет повторную синхронизацию вашего эффекта. Это работает и для функций: @@ -932,15 +928,15 @@ function ChatRoom() { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, []); // ✅ Объявлены все зависимости + }, []); // ✅ Все зависимости объявлены // ... ``` -Поскольку `createOptions` объявлена за пределами вашего компонента, она не является реактивным значением. Вот почему ее не нужно указывать в зависимостях вашего Effect, и почему она никогда не приведет к повторной синхронизации вашего Effect. +Поскольку `createOptions` объявлена вне вашего компонента, это не реактивное значение. Вот почему его не нужно указывать в зависимостях вашего эффекта, и почему оно никогда не вызовет повторную синхронизацию вашего эффекта. -#### Переместите динамические объекты и функции внутрь вашего Effect {/*move-dynamic-objects-and-functions-inside-your-effect*/} +#### Перенести динамические объекты и функции внутрь вашего эффекта {/*move-dynamic-objects-and-functions-inside-your-effect*/} -Если ваш объект зависит от какого-либо реактивного значения, которое может измениться в результате перерендеринга, например, пропса `roomId`, вы не можете вынести его *за пределы* вашего компонента. Однако вы можете переместить его создание *внутрь* кода вашего Effect: +Если ваш объект зависит от какого-либо реактивного значения, которое может измениться в результате повторного рендеринга, например, проп `roomId`, вы не можете вынести его *за пределы* вашего компонента. Однако вы можете переместить его создание *внутрь* кода вашего эффекта: ```js {7-10,11,14} const serverUrl = 'https://localhost:1234'; @@ -956,11 +952,11 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ Объявлены все зависимости + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -Теперь, когда `options` объявлен внутри вашего Effect, он больше не является зависимостью вашего Effect. Вместо этого единственным реактивным значением, используемым вашим Effect, является `roomId`. Поскольку `roomId` не является объектом или функцией, вы можете быть уверены, что он не будет *непреднамеренно* другим. В JavaScript числа и строки сравниваются по их содержимому: +Теперь, когда `options` объявлен внутри вашего эффекта, он больше не является зависимостью вашего эффекта. Вместо этого единственным реактивным значением, используемым вашим эффектом, является `roomId`. Поскольку `roomId` не является объектом или функцией, вы можете быть уверены, что он не будет *непреднамеренно* отличаться. В JavaScript числа и строки сравниваются по их содержимому: ```js {7-8} // Во время первого рендеринга @@ -1028,7 +1024,7 @@ export default function App() { ```js src/chat.js export function createConnection({ serverUrl, roomId }) { - // Реальная реализация на самом деле подключится к серверу + // Реальная реализация действительно подключится к серверу return { connect() { console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...'); @@ -1047,7 +1043,7 @@ button { margin-left: 10px; } -Однако он *действительно* переподключается, когда вы меняете выпадающий список `roomId`, как и ожидалось. +Однако он *переподключается*, когда вы меняете выпадающий список `roomId`, как и ожидалось. Это работает и для функций: @@ -1069,13 +1065,13 @@ function ChatRoom({ roomId }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [roomId]); // ✅ Объявлены все зависимости + }, [roomId]); // ✅ Все зависимости объявлены // ... ``` -Вы можете написать свои собственные функции для группировки частей логики внутри вашего Effect. Пока вы также объявляете их *внутри* вашего Effect, они не являются реактивными значениями, поэтому они не должны быть зависимостями вашего Effect. +Вы можете писать свои собственные функции для группировки фрагментов логики внутри вашего эффекта. Пока вы также объявляете их *внутри* вашего эффекта, они не являются реактивными значениями, и поэтому им не нужно быть зависимостями вашего эффекта. -#### Считывайте примитивные значения из объектов {/*read-primitive-values-from-objects*/} +#### Чтение примитивных значений из объектов {/*read-primitive-values-from-objects*/} Иногда вы можете получать объект из пропсов: @@ -1087,11 +1083,11 @@ function ChatRoom({ options }) { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); - }, [options]); // ✅ Объявлены все зависимости + }, [options]); // ✅ Все зависимости объявлены // ... ``` -Риск здесь заключается в том, что родительский компонент будет создавать объект во время рендеринга: +Риск здесь в том, что родительский компонент будет создавать объект во время рендеринга: ```js {3-6} ``` -Это привело бы к повторному подключению вашего Effect каждый раз, когда родительский компонент перерендеривается. Чтобы исправить это, считывайте информацию из объекта *за пределами* Effect и избегайте зависимостей от объектов и функций: +Это приведёт к тому, что ваш эффект будет переподключаться при каждом повторном рендеринге родительского компонента. Чтобы исправить это, читайте информацию из объекта *вне* эффекта и избегайте зависимостей объектов и функций: ```js {4,7-8,12} function ChatRoom({ options }) { @@ -1117,15 +1113,15 @@ function ChatRoom({ options }) { }); connection.connect(); return () => connection.disconnect(); - }, [roomId, serverUrl]); // ✅ Объявлены все зависимости + }, [roomId, serverUrl]); // ✅ Все зависимости объявлены // ... ``` -Логика становится немного повторяющейся (вы считываете некоторые значения из объекта за пределами Effect, а затем создаете объект с теми же значениями внутри Effect). Но это делает очень явным, от какой информации *фактически* зависит ваш Effect. Если объект непреднамеренно пересоздается родительским компонентом, чат не будет переподключаться. Однако, если `options.roomId` или `options.serverUrl` действительно отличаются, чат переподключится. +Логика становится немного повторяющейся (вы читаете некоторые значения из объекта вне эффекта, а затем создаёте объект с теми же значениями внутри эффекта). Но это делает очень явным, от какой информации ваш эффект *действительно* зависит. Если объект непреднамеренно воссоздаётся родительским компонентом, чат не будет переподключаться. Однако, если `options.roomId` или `options.serverUrl` действительно отличаются, чат переподключится. -#### Вычисляйте примитивные значения из функций {/*calculate-primitive-values-from-functions*/} +#### Вычисление примитивных значений из функций {/*calculate-primitive-values-from-functions*/} -Тот же подход может работать и для функций. Например, предположим, что родительский компонент передает функцию: +Тот же подход может работать и для функций. Например, предположим, родительский компонент передаёт функцию: ```js {3-8} ``` -Чтобы избежать превращения ее в зависимость (и вызывать повторное подключение при перерендеринге), вызовите ее за пределами Effect. Это даст вам значения `roomId` и `serverUrl`, которые не являются объектами и которые вы можете считывать изнутри вашего Effect: +Чтобы избежать её включения в зависимости (и, следовательно, повторных переподключений при рендерингах), вызовите её вне эффекта. Это даст вам значения `roomId` и `serverUrl`, которые не являются объектами и которые вы можете читать внутри вашего эффекта: ```js {1,4} function ChatRoom({ getOptions }) { @@ -1153,37 +1149,36 @@ function ChatRoom({ getOptions }) { }); connection.connect(); return () => connection.disconnect(); - }, [roomId, serverUrl]); // ✅ Объявлены все зависимости + }, [roomId, serverUrl]); // ✅ Все зависимости объявлены // ... ``` -Это работает только для [чистых](/learn/keeping-components-pure) функций, потому что их безопасно вызывать во время рендеринга. Если ваша функция является обработчиком событий, но вы не хотите, чтобы ее изменения повторно синхронизировали ваш Effect, [оберните ее вместо этого в Effect Event.](#do-you-want-to-read-a-value-without-reacting-to-its-changes) +Это работает только для [чистых](/learn/keeping-components-pure) функций, поскольку их безопасно вызывать во время рендеринга. Если ваша функция является обработчиком событий, но вы не хотите, чтобы её изменения повторно синхронизировали ваш эффект, [вместо этого оберните её в Effect Event.](#do-you-want-to-read-a-value-without-reacting-to-its-changes) - Зависимости всегда должны соответствовать коду. -- Когда вы недовольны своими зависимостями, вам нужно отредактировать код. -- Подавление линтера приводит к очень запутанным ошибкам, и вам всегда следует избегать этого. +- Когда вы недовольны своими зависимостями, вам нужно редактировать код. +- Подавление линтера приводит к очень запутанным ошибкам, и вам всегда следует его избегать. - Чтобы удалить зависимость, вам нужно «доказать» линтеру, что она не нужна. -- Если какой-то код должен выполняться в ответ на определенное взаимодействие, переместите этот код в обработчик событий. -- Если разные части вашего Effect должны перезапускаться по разным причинам, разделите его на несколько Effects. -- Если вы хотите обновить какое-либо состояние на основе предыдущего состояния, передайте функцию обновления. -- Если вы хотите прочитать последнее значение, не «реагируя» на него, извлеките Effect Event из вашего Effect. +- Если какой-то код должен выполняться в ответ на определённое взаимодействие, переместите этот код в обработчик событий. +- Если разные части вашего эффекта должны перезапускаться по разным причинам, разделите его на несколько эффектов. +- Если вы хотите обновить какое-то состояние на основе предыдущего состояния, передайте функцию-обновитель. +- Если вы хотите прочитать последнее значение, не «реагируя» на него, извлеките Effect Event из вашего эффекта. - В JavaScript объекты и функции считаются разными, если они были созданы в разное время. -- Старайтесь избегать зависимостей от объектов и функций. Переместите их за пределы компонента или внутрь Effect. +- Старайтесь избегать зависимостей объектов и функций. Переместите их за пределы компонента или внутрь эффекта. +#### Исправить сбрасываемый интервал {/*fix-a-resetting-interval*/} -#### Исправьте сбрасывающийся интервал {/*fix-a-resetting-interval*/} - -Этот эффект настраивает интервал, который срабатывает каждую секунду. Вы заметили странную вещь: кажется, что интервал уничтожается и пересоздается каждый раз, когда срабатывает. Исправьте код, чтобы интервал не пересоздавался постоянно. +Этот эффект настраивает интервал, который срабатывает каждую секунду. Вы заметили что-то странное: кажется, что интервал уничтожается и воссоздаётся каждый раз, когда срабатывает. Исправьте код так, чтобы интервал не воссоздавался постоянно. -Кажется, что код этого эффекта зависит от `count`. Есть ли способ обойтись без этой зависимости? Должен быть способ обновить состояние `count` на основе его предыдущего значения, не добавляя зависимость от этого значения. +Кажется, что код этого эффекта зависит от `count`. Есть ли способ не нуждаться в этой зависимости? Должен быть способ обновить состояние `count` на основе его предыдущего значения, не добавляя зависимость от этого значения. @@ -1196,18 +1191,18 @@ export default function Timer() { const [count, setCount] = useState(0); useEffect(() => { - console.log('✅ Создание интервала'); + console.log('✅ Creating an interval'); const id = setInterval(() => { - console.log('⏰ Тик интервала'); + console.log('⏰ Interval tick'); setCount(count + 1); }, 1000); return () => { - console.log('❌ Очистка интервала'); + console.log('❌ Clearing an interval'); clearInterval(id); }; }, [count]); - return

Счётчик: {count}

+ return

Counter: {count}

} ``` @@ -1215,9 +1210,9 @@ export default function Timer() { -Вы хотите обновить состояние `count`, чтобы оно было `count + 1` изнутри эффекта. Однако это делает ваш эффект зависимым от `count`, который изменяется с каждым тиком, и поэтому ваш интервал пересоздается при каждом тике. +Вы хотите обновить состояние `count` до `count + 1` изнутри эффекта. Однако это делает ваш эффект зависимым от `count`, который меняется с каждым тиком, и именно поэтому ваш интервал воссоздаётся при каждом тике. -Чтобы решить эту проблему, используйте [функцию обновления](/reference/react/useState#updating-state-based-on-the-previous-state) и напишите `setCount(c => c + 1)` вместо `setCount(count + 1)`: +Чтобы решить эту проблему, используйте [функцию-обновитель](/reference/react/useState#updating-state-based-on-the-previous-state) и напишите `setCount(c => c + 1)` вместо `setCount(count + 1)`: @@ -1228,36 +1223,36 @@ export default function Timer() { const [count, setCount] = useState(0); useEffect(() => { - console.log('✅ Создание интервала'); + console.log('✅ Creating an interval'); const id = setInterval(() => { - console.log('⏰ Тик интервала'); + console.log('⏰ Interval tick'); setCount(c => c + 1); }, 1000); return () => { - console.log('❌ Очистка интервала'); + console.log('❌ Clearing an interval'); clearInterval(id); }; }, []); - return

Счётчик: {count}

+ return

Counter: {count}

} ```
-Вместо чтения `count` внутри эффекта, вы передаете инструкцию `c => c + 1` («увеличить это число!») в React. React применит ее при следующем рендере. И поскольку вам больше не нужно читать значение `count` внутри вашего эффекта, вы можете оставить зависимости вашего эффекта пустыми (`[]`). Это предотвращает пересоздание интервала вашим эффектом при каждом тике. +Вместо чтения `count` внутри эффекта вы передаёте инструкцию `c => c + 1` («увеличить это число!») React. React применит её при следующем рендеринге. И поскольку вам больше не нужно читать значение `count` внутри вашего эффекта, вы можете оставить зависимости вашего эффекта пустыми (`[]`). Это предотвращает воссоздание интервала вашим эффектом при каждом тике.
-#### Исправьте повторное срабатывание анимации {/*fix-a-retriggering-animation*/} +#### Исправить повторно запускаемую анимацию {/*fix-a-retriggering-animation*/} -В этом примере, когда вы нажимаете «Показать», появляется приветственное сообщение. Анимация занимает секунду. Когда вы нажимаете «Удалить», приветственное сообщение сразу исчезает. Логика анимации появления реализована в файле `animation.js` в виде простого JavaScript [цикла анимации.](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) Вам не нужно изменять эту логику. Вы можете рассматривать ее как стороннюю библиотеку. Ваш эффект создает экземпляр `FadeInAnimation` для узла DOM, а затем вызывает `start(duration)` или `stop()` для управления анимацией. `duration` управляется ползунком. Настройте ползунок и посмотрите, как меняется анимация. +В этом примере при нажатии «Show» появляется приветственное сообщение, которое плавно появляется в течение секунды. При нажатии «Remove» приветственное сообщение немедленно исчезает. Логика анимации появления реализована в файле `animation.js` как обычный JavaScript [цикл анимации.](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) Вам не нужно менять эту логику. Вы можете рассматривать её как стороннюю библиотеку. Ваш эффект создаёт экземпляр `FadeInAnimation` для DOM-узла, а затем вызывает `start(duration)` или `stop()` для управления анимацией. `duration` контролируется ползунком. Настройте ползунок и посмотрите, как меняется анимация. -Этот код уже работает, но есть кое-что, что вы хотите изменить. В настоящее время, когда вы перемещаете ползунок, который управляет переменной состояния `duration`, это повторно запускает анимацию. Измените поведение так, чтобы эффект не «реагировал» на переменную `duration`. Когда вы нажимаете «Показать», эффект должен использовать текущее значение `duration` на ползунке. Однако перемещение самого ползунка не должно само по себе повторно запускать анимацию. +Этот код уже работает, но есть кое-что, что вы хотите изменить. В настоящее время при перемещении ползунка, который управляет переменной состояния `duration`, анимация запускается повторно. Измените поведение так, чтобы эффект не «реагировал» на переменную `duration`. При нажатии «Show» эффект должен использовать текущее значение `duration` на ползунке. Однако само перемещение ползунка не должно запускать анимацию повторно. -Есть ли строка кода внутри эффекта, которая не должна быть реактивной? Как можно переместить нереактивный код из эффекта? +Есть ли строка кода внутри эффекта, которая не должна быть реактивной? Как вы можете вынести нереактивный код из эффекта? @@ -1386,7 +1381,7 @@ html, body { min-height: 300px; } -Вашему эффекту необходимо прочитать последнее значение `duration`, но вы не хотите, чтобы он «реагировал» на изменения в `duration`. Вы используете `duration`, чтобы запустить анимацию, но запуск анимации не является реактивным. Извлеките нереактивную строку кода в событие эффекта и вызовите эту функцию из вашего эффекта. +Вашему эффекту необходимо считывать последнее значение `duration`, но вы не хотите, чтобы он «реагировал» на изменения `duration`. Вы используете `duration` для запуска анимации, но запуск анимации не является реактивным. Вынесите нереактивную строку кода в Effect Event и вызовите эту функцию из вашего эффекта. @@ -1509,20 +1504,19 @@ html, body { min-height: 300px; } -События эффекта, такие как `onAppear`, не являются реактивными, поэтому вы можете прочитать `duration` внутри, не перезапуская анимацию. +Effect Events, такие как `onAppear`, не являются реактивными, поэтому вы можете читать `duration` внутри них, не вызывая повторный запуск анимации. +#### Исправьте переподключающийся чат {/*fix-a-reconnecting-chat*/} -#### Исправьте переподключение чата {/*fix-a-reconnecting-chat*/} - -В этом примере каждый раз, когда вы нажимаете «Toggle theme», чат переподключается. Почему это происходит? Исправьте ошибку, чтобы чат переподключался только при изменении URL-адреса сервера или выборе другой комнаты чата. +В этом примере чат переподключается каждый раз, когда вы нажимаете «Переключить тему». Почему это происходит? Исправьте ошибку так, чтобы чат переподключался только при изменении URL сервера или выборе другой комнаты чата. -Рассматривайте `chat.js` как внешнюю стороннюю библиотеку: вы можете обращаться к ней, чтобы проверить ее API, но не редактируйте ее. +Считайте `chat.js` внешней сторонней библиотекой: вы можете обращаться к ней для проверки её API, но не редактируйте её. -Есть несколько способов исправить это, но в конечном итоге вам нужно избегать использования объекта в качестве зависимости. +Существует несколько способов исправить это, но в конечном итоге вам следует избегать использования объекта в качестве зависимости. @@ -1616,9 +1610,9 @@ label, button { display: block; margin-bottom: 5px; } -Ваш Effect перезапускается, потому что он зависит от объекта `options`. Объекты могут быть непреднамеренно пересозданы, поэтому вам следует избегать их использования в качестве зависимостей ваших Effects, когда это возможно. +Ваш Effect перезапускается, потому что он зависит от объекта `options`. Объекты могут быть созданы заново непреднамеренно, поэтому вам следует избегать их в качестве зависимостей ваших Effects, когда это возможно. -Наименее инвазивное исправление — прочитать `roomId` и `serverUrl` прямо за пределами Effect, а затем сделать Effect зависимым от этих примитивных значений (которые не могут измениться непреднамеренно). Внутри Effect создайте объект и передайте его в `createConnection`: +Наименее инвазивное исправление — это извлечь `roomId` и `serverUrl` непосредственно перед Effect и сделать Effect зависимым от этих примитивных значений (которые не могут быть изменены непреднамеренно). Внутри Effect создайте объект и передайте его в `createConnection`: @@ -1712,7 +1706,7 @@ label, button { display: block; margin-bottom: 5px; } -Еще лучше заменить проп `options` объекта на более конкретные пропсы `roomId` и `serverUrl`: +Было бы еще лучше заменить объект `options` на более конкретные пропсы `roomId` и `serverUrl`: @@ -1803,25 +1797,25 @@ label, button { display: block; margin-bottom: 5px; } -Придерживаясь примитивных пропсов, где это возможно, упрощается оптимизация ваших компонентов в дальнейшем. +Использование примитивных пропсов, где это возможно, упрощает последующую оптимизацию компонентов. -#### Исправьте повторное подключение к чату, снова {/*fix-a-reconnecting-chat-again*/} +#### Снова исправьте переподключающийся чат {/*fix-a-reconnecting-chat-again*/} -Этот пример подключается к чату с шифрованием или без него. Переключите флажок и обратите внимание на разные сообщения в консоли, когда шифрование включено и выключено. Попробуйте сменить комнату. Затем попробуйте переключить тему. Когда вы подключены к чат-комнате, вы будете получать новые сообщения каждые несколько секунд. Убедитесь, что их цвет соответствует выбранной вами теме. +Этот пример подключается к чату с шифрованием или без него. Переключите флажок и обратите внимание на разные сообщения в консоли при включенном и выключенном шифровании. Попробуйте сменить комнату. Затем попробуйте переключить тему. Когда вы подключены к комнате чата, вы будете получать новые сообщения каждые несколько секунд. Убедитесь, что их цвет соответствует выбранной вами теме. -В этом примере чат переподключается каждый раз, когда вы пытаетесь изменить тему. Исправьте это. После исправления изменение темы не должно приводить к повторному подключению чата, но переключение настроек шифрования или изменение комнаты должно приводить к повторному подключению. +В этом примере чат переподключается каждый раз, когда вы пытаетесь сменить тему. Исправьте это. После исправления смена темы не должна вызывать переподключение чата, но переключение настроек шифрования или смена комнаты должны вызывать переподключение. -Не изменяйте код в `chat.js`. В остальном вы можете изменить любой код, если это приводит к тому же поведению. Например, вам может быть полезно изменить, какие пропсы передаются вниз. +Не меняйте код в `chat.js`. В остальном вы можете менять любой код, если это приведет к такому же поведению. Например, вам может быть полезно изменить передаваемые пропсы. -Вы передаете две функции: `onMessage` и `createConnection`. Обе они создаются с нуля каждый раз, когда `App` перерендеривается. Они считаются новыми значениями каждый раз, поэтому они повторно запускают ваш Effect. +Вы передаете две функции: `onMessage` и `createConnection`. Обе они создаются заново при каждом рендере `App`. Они считаются новыми значениями каждый раз, поэтому они повторно запускают ваш Effect. -Одна из этих функций является обработчиком событий. Знаете ли вы какой-нибудь способ вызвать обработчик событий в Effect, не «реагируя» на новые значения функции обработчика событий? Это пригодится! +Одну из этих функций можно обработать с помощью `useEffectEvent`. Есть ли способ вызвать обработчик событий в Effect без "реакции" на новые значения функции обработчика? Это было бы полезно! -Другая из этих функций существует только для передачи некоторого состояния в импортированный метод API. Действительно ли эта функция необходима? Какая существенная информация передается вниз? Возможно, вам потребуется переместить некоторые импорты из `App.js` в `ChatRoom.js`. +Другая из этих функций существует только для передачи некоторой информации в метод импортируемого API. Действительно ли эта функция необходима? Какая основная информация передается? Возможно, вам придется переместить некоторые импорты из `App.js` в `ChatRoom.js`. @@ -2036,11 +2030,11 @@ label, button { display: block; margin-bottom: 5px; } -Существует более одного правильного способа решения этой задачи, но вот одно из возможных решений. +Существует несколько правильных способов решения этой проблемы, но вот один из возможных. -В исходном примере переключение темы приводило к созданию и передаче разных функций `onMessage` и `createConnection`. Поскольку Effect зависел от этих функций, чат переподключался каждый раз, когда вы переключали тему. +В исходном примере переключение темы приводило к созданию и передаче разных функций `onMessage` и `createConnection`. Поскольку Effect зависел от этих функций, чат переподключался каждый раз при переключении темы. -Чтобы исправить проблему с `onMessage`, вам нужно было обернуть его в Effect Event: +Чтобы исправить проблему с `onMessage`, вам нужно было обернуть ее в Effect Event: ```js {1,2,6} export default function ChatRoom({ roomId, createConnection, onMessage }) { @@ -2052,9 +2046,9 @@ export default function ChatRoom({ roomId, createConnection, onMessage }) { // ... ``` -В отличие от пропса `onMessage`, Effect Event `onReceiveMessage` не является реактивным. Вот почему он не должен быть зависимостью вашего Effect. В результате изменения `onMessage` не приведут к повторному подключению чата. +В отличие от пропса `onMessage`, Effect Event `onReceiveMessage` не является реактивным. Поэтому он не должен быть зависимостью вашего Effect. В результате изменения `onMessage` не вызовут переподключение чата. -Вы не можете сделать то же самое с `createConnection`, потому что он *должен* быть реактивным. Вы *хотите*, чтобы Effect перезапускался, если пользователь переключается между зашифрованным и незашифрованным подключением, или если пользователь переключает текущую комнату. Однако, поскольку `createConnection` является функцией, вы не можете проверить, *действительно* ли изменилась информация, которую она считывает, или нет. Чтобы решить эту проблему, вместо передачи `createConnection` из компонента `App`, передайте необработанные значения `roomId` и `isEncrypted`: +Вы не можете сделать то же самое с `createConnection`, потому что он *должен* быть реактивным. Вы *хотите*, чтобы Effect перезапускался, если пользователь переключается между зашифрованным и незашифрованным соединением, или если пользователь меняет текущую комнату. Однако, поскольку `createConnection` является функцией, вы не можете проверить, изменилась ли информация, которую она читает, *фактически*. Чтобы решить эту проблему, вместо передачи `createConnection` из компонента `App`, передайте необработанные значения `roomId` и `isEncrypted`: ```js {2-3} ``` -Теперь вы можете переместить функцию `createConnection` *внутрь* Effect, а не передавать ее из `App`: +Теперь вы можете переместить функцию `createConnection` *внутрь* Effect вместо передачи ее из компонента `App`: ```js {1-4,6,10-20} import { @@ -2092,19 +2086,19 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // ... ``` -После этих двух изменений ваш Effect больше не зависит от каких-либо значений функций: +После этих двух изменений ваш Effect больше не зависит от значений функций: ```js {1,8,10,21} -export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reactive values - const onReceiveMessage = useEffectEvent(onMessage); // Not reactive +export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Реактивные значения + const onReceiveMessage = useEffectEvent(onMessage); // Не реактивное useEffect(() => { function createConnection() { const options = { serverUrl: 'https://localhost:1234', - roomId: roomId // Reading a reactive value + roomId: roomId // Чтение реактивного значения }; - if (isEncrypted) { // Reading a reactive value + if (isEncrypted) { // Чтение реактивного значения return createEncryptedConnection(options); } else { return createUnencryptedConnection(options); @@ -2115,10 +2109,10 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reacti connection.on('message', (msg) => onReceiveMessage(msg)); connection.connect(); return () => connection.disconnect(); - }, [roomId, isEncrypted]); // ✅ All dependencies declared + }, [roomId, isEncrypted]); // ✅ Все зависимости объявлены ``` -В результате чат переподключается только тогда, когда что-то значимое (`roomId` или `isEncrypted`) меняется: +В результате чат переподключается только тогда, когда изменяется что-то значимое (`roomId` или `isEncrypted`): @@ -2226,23 +2220,20 @@ export default function ChatRoom({ roomId, isEncrypted, onMessage }) { } ``` - - -``` ```js src/chat.js export function createEncryptedConnection({ serverUrl, roomId }) { - // Реальная реализация на самом деле подключится к серверу + // A real implementation would actually connect to the server if (typeof serverUrl !== 'string') { - throw Error('Ожидалось, что serverUrl будет строкой. Получено: ' + serverUrl); + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); } if (typeof roomId !== 'string') { - throw Error('Ожидалось, что roomId будет строкой. Получено: ' + roomId); + throw Error('Expected roomId to be a string. Received: ' + roomId); } let intervalId; let messageCallback; return { connect() { - console.log('✅ 🔐 Подключение к комнате "' + roomId + '"... (зашифровано)'); + console.log('✅ 🔐 Connecting to "' + roomId + '" room... (encrypted)'); clearInterval(intervalId); intervalId = setInterval(() => { if (messageCallback) { @@ -2257,14 +2248,14 @@ export function createEncryptedConnection({ serverUrl, roomId }) { disconnect() { clearInterval(intervalId); messageCallback = null; - console.log('❌ 🔐 Отключение от комнаты "' + roomId + '" (зашифровано)'); + console.log('❌ 🔐 Disconnected from "' + roomId + '" room (encrypted)'); }, on(event, callback) { if (messageCallback) { - throw Error('Нельзя добавить обработчик дважды.'); + throw Error('Cannot add the handler twice.'); } if (event !== 'message') { - throw Error('Поддерживается только событие "message".'); + throw Error('Only "message" event is supported.'); } messageCallback = callback; }, @@ -2272,18 +2263,18 @@ export function createEncryptedConnection({ serverUrl, roomId }) { } export function createUnencryptedConnection({ serverUrl, roomId }) { - // Реальная реализация на самом деле подключится к серверу + // A real implementation would actually connect to the server if (typeof serverUrl !== 'string') { - throw Error('Ожидалось, что serverUrl будет строкой. Получено: ' + serverUrl); + throw Error('Expected serverUrl to be a string. Received: ' + serverUrl); } if (typeof roomId !== 'string') { - throw Error('Ожидалось, что roomId будет строкой. Получено: ' + roomId); + throw Error('Expected roomId to be a string. Received: ' + roomId); } let intervalId; let messageCallback; return { connect() { - console.log('✅ Подключение к комнате "' + roomId + '" (незашифровано)...'); + console.log('✅ Connecting to "' + roomId + '" room (unencrypted)...'); clearInterval(intervalId); intervalId = setInterval(() => { if (messageCallback) { @@ -2298,14 +2289,14 @@ export function createUnencryptedConnection({ serverUrl, roomId }) { disconnect() { clearInterval(intervalId); messageCallback = null; - console.log('❌ Отключение от комнаты "' + roomId + '" (незашифровано)'); + console.log('❌ Disconnected from "' + roomId + '" room (unencrypted)'); }, on(event, callback) { if (messageCallback) { - throw Error('Нельзя добавить обработчик дважды.'); + throw Error('Cannot add the handler twice.'); } if (event !== 'message') { - throw Error('Поддерживается только событие "message".'); + throw Error('Only "message" event is supported.'); } messageCallback = callback; }, @@ -2339,5 +2330,4 @@ label, button { display: block; margin-bottom: 5px; } -
-``` \ No newline at end of file + \ No newline at end of file From 88268ef1cab9fa32ccbd0ea15b81d35bbaf668f2 Mon Sep 17 00:00:00 2001 From: nivaldo Date: Mon, 18 May 2026 14:56:21 -0300 Subject: [PATCH 4/4] chore: keep frontmatter title as-is --- src/content/learn/removing-effect-dependencies.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/content/learn/removing-effect-dependencies.md b/src/content/learn/removing-effect-dependencies.md index 665fdfbd27..48b43b6d39 100644 --- a/src/content/learn/removing-effect-dependencies.md +++ b/src/content/learn/removing-effect-dependencies.md @@ -1,6 +1,7 @@ --- -title: 'Удаление зависимостей эффекта' +title: 'Removing Effect Dependencies' --- + Когда вы пишете Effect, линтер проверяет, включили ли вы все реактивные значения (например, пропсы и состояние), которые Effect читает, в список зависимостей вашего Effect. Это гарантирует, что ваш Effect остается синхронизированным с последними пропсами и состоянием вашего компонента. Ненужные зависимости могут привести к тому, что ваш Effect будет выполняться слишком часто или даже создаст бесконечный цикл. Следуйте этому руководству, чтобы просмотреть и удалить ненужные зависимости из ваших Effects. @@ -2330,4 +2331,4 @@ label, button { display: block; margin-bottom: 5px; } - \ No newline at end of file +