From 5589239b2d77e8e1bba9a1de9b07fc58f423f0e1 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:06:23 +0000 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20translate=20`lifecycle-of-reactive-?= =?UTF-8?q?effects.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/lifecycle-of-reactive-effects.md | 432 +++++++++--------- 1 file changed, 216 insertions(+), 216 deletions(-) diff --git a/src/content/learn/lifecycle-of-reactive-effects.md b/src/content/learn/lifecycle-of-reactive-effects.md index 3dc9a75f02..c650b15733 100644 --- a/src/content/learn/lifecycle-of-reactive-effects.md +++ b/src/content/learn/lifecycle-of-reactive-effects.md @@ -1,37 +1,37 @@ --- -title: 'Lifecycle of Reactive Effects' +title: 'Жизненный цикл реактивных эффектов' --- -Effects have a different lifecycle from components. Components may mount, update, or unmount. An Effect can only do two things: to start synchronizing something, and later to stop synchronizing it. This cycle can happen multiple times if your Effect depends on props and state that change over time. React provides a linter rule to check that you've specified your Effect's dependencies correctly. This keeps your Effect synchronized to the latest props and state. +Эффекты имеют другой жизненный цикл по сравнению с компонентами. Компоненты могут монтироваться, обновляться или размонтироваться. Эффект может делать только две вещи: начать синхронизацию чего-либо и позже прекратить её. Этот цикл может повторяться несколько раз, если ваш эффект зависит от пропсов и состояния, которые меняются со временем. React предоставляет правило линтера для проверки правильности указания зависимостей вашего эффекта. Это гарантирует синхронизацию вашего эффекта с последними пропсами и состоянием. -- How an Effect's lifecycle is different from a component's lifecycle -- How to think about each individual Effect in isolation -- When your Effect needs to re-synchronize, and why -- How your Effect's dependencies are determined -- What it means for a value to be reactive -- What an empty dependency array means -- How React verifies your dependencies are correct with a linter -- What to do when you disagree with the linter +- Чем жизненный цикл эффекта отличается от жизненного цикла компонента +- Как рассматривать каждый отдельный эффект в изоляции +- Когда вашему эффекту нужно повторно синхронизироваться и почему +- Как определяются зависимости вашего эффекта +- Что означает реактивность значения +- Что означает пустой массив зависимостей +- Как React проверяет правильность ваших зависимостей с помощью линтера +- Что делать, если вы не согласны с линтером -## The lifecycle of an Effect {/*the-lifecycle-of-an-effect*/} +## Жизненный цикл эффекта {/*the-lifecycle-of-an-effect*/} -Every React component goes through the same lifecycle: +Каждый компонент React проходит через один и тот же жизненный цикл: -- A component _mounts_ when it's added to the screen. -- A component _updates_ when it receives new props or state, usually in response to an interaction. -- A component _unmounts_ when it's removed from the screen. +- Компонент _монтируется_, когда он добавляется на экран. +- Компонент _обновляется_, когда он получает новые пропсы или состояние, обычно в ответ на взаимодействие. +- Компонент _размонтируется_, когда он удаляется с экрана. -**It's a good way to think about components, but _not_ about Effects.** Instead, try to think about each Effect independently from your component's lifecycle. An Effect describes how to [synchronize an external system](/learn/synchronizing-with-effects) to the current props and state. As your code changes, synchronization will need to happen more or less often. +**Это хороший способ думать о компонентах, но _не_ об эффектах.** Вместо этого старайтесь думать о каждом эффекте независимо от жизненного цикла вашего компонента. Эффект описывает, как [синхронизировать внешнюю систему](/learn/synchronizing-with-effects) с текущими пропсами и состоянием. По мере изменения вашего кода синхронизация будет происходить чаще или реже. -To illustrate this point, consider this Effect connecting your component to a chat server: +Чтобы проиллюстрировать это, рассмотрим эффект, подключающий ваш компонент к чат-серверу: ```js const serverUrl = 'https://localhost:1234'; @@ -48,7 +48,7 @@ function ChatRoom({ roomId }) { } ``` -Your Effect's body specifies how to **start synchronizing:** +Тело вашего эффекта определяет, как **начать синхронизацию:** ```js {2-3} // ... @@ -60,7 +60,7 @@ Your Effect's body specifies how to **start synchronizing:** // ... ``` -The cleanup function returned by your Effect specifies how to **stop synchronizing:** +Функция очистки, возвращаемая вашим эффектом, определяет, как **прекратить синхронизацию:** ```js {5} // ... @@ -72,19 +72,19 @@ The cleanup function returned by your Effect specifies how to **stop synchronizi // ... ``` -Intuitively, you might think that React would **start synchronizing** when your component mounts and **stop synchronizing** when your component unmounts. However, this is not the end of the story! Sometimes, it may also be necessary to **start and stop synchronizing multiple times** while the component remains mounted. +Интуитивно вы можете подумать, что React будет **начинать синхронизацию**, когда ваш компонент монтируется, и **прекращать синхронизацию**, когда ваш компонент размонтируется. Однако это еще не все! Иногда может потребоваться **начинать и прекращать синхронизацию несколько раз**, пока компонент остается смонтированным. -Let's look at _why_ this is necessary, _when_ it happens, and _how_ you can control this behavior. +Давайте посмотрим, _почему_ это необходимо, _когда_ это происходит и _как_ вы можете контролировать это поведение. -Some Effects don't return a cleanup function at all. [More often than not,](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) you'll want to return one--but if you don't, React will behave as if you returned an empty cleanup function. +Некоторые эффекты вообще не возвращают функцию очистки. [Чаще всего](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) вы захотите ее вернуть — но если нет, React будет вести себя так, как будто вы вернули пустую функцию очистки. -### Why synchronization may need to happen more than once {/*why-synchronization-may-need-to-happen-more-than-once*/} +### Почему синхронизация может потребоваться более одного раза {/*why-synchronization-may-need-to-happen-more-than-once*/} -Imagine this `ChatRoom` component receives a `roomId` prop that the user picks in a dropdown. Let's say that initially the user picks the `"general"` room as the `roomId`. Your app displays the `"general"` chat room: +Представьте, что компонент `ChatRoom` получает пропс `roomId`, который пользователь выбирает в выпадающем списке. Допустим, изначально пользователь выбирает комнату `"general"` в качестве `roomId`. Ваше приложение отображает чат-комнату `"general"`: ```js {3} const serverUrl = 'https://localhost:1234'; @@ -95,23 +95,23 @@ function ChatRoom({ roomId /* "general" */ }) { } ``` -After the UI is displayed, React will run your Effect to **start synchronizing.** It connects to the `"general"` room: +После отображения пользовательского интерфейса React запустит ваш эффект для **начала синхронизации.** Он подключается к комнате `"general"`: ```js {3,4} function ChatRoom({ roomId /* "general" */ }) { useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Connects to the "general" room + const connection = createConnection(serverUrl, roomId); // Подключается к комнате "general" connection.connect(); return () => { - connection.disconnect(); // Disconnects from the "general" room + connection.disconnect(); // Отключается от комнаты "general" }; }, [roomId]); // ... ``` -So far, so good. +Пока все хорошо. -Later, the user picks a different room in the dropdown (for example, `"travel"`). First, React will update the UI: +Позже пользователь выбирает другую комнату в выпадающем списке (например, `"travel"`). Сначала React обновит пользовательский интерфейс: ```js {1} function ChatRoom({ roomId /* "travel" */ }) { @@ -120,93 +120,93 @@ function ChatRoom({ roomId /* "travel" */ }) { } ``` -Think about what should happen next. The user sees that `"travel"` is the selected chat room in the UI. However, the Effect that ran the last time is still connected to the `"general"` room. **The `roomId` prop has changed, so what your Effect did back then (connecting to the `"general"` room) no longer matches the UI.** +Подумайте, что должно произойти дальше. Пользователь видит, что `"travel"` — это выбранная комната чата в пользовательском интерфейсе. Однако эффект, который был запущен в прошлый раз, все еще подключен к комнате `"general"`. **Пропс `roomId` изменился, поэтому то, что ваш эффект сделал тогда (подключение к комнате `"general"`), больше не соответствует пользовательскому интерфейсу.** -At this point, you want React to do two things: +В этот момент вы хотите, чтобы React сделал две вещи: -1. Stop synchronizing with the old `roomId` (disconnect from the `"general"` room) -2. Start synchronizing with the new `roomId` (connect to the `"travel"` room) +1. Прекратить синхронизацию со старым `roomId` (отключиться от комнаты `"general"`) +2. Начать синхронизацию с новым `roomId` (подключиться к комнате `"travel"`) -**Luckily, you've already taught React how to do both of these things!** Your Effect's body specifies how to start synchronizing, and your cleanup function specifies how to stop synchronizing. All that React needs to do now is to call them in the correct order and with the correct props and state. Let's see how exactly that happens. +**К счастью, вы уже научили React делать обе эти вещи!** Тело вашего эффекта определяет, как начать синхронизацию, а ваша функция очистки определяет, как прекратить синхронизацию. Все, что нужно сделать React, — это вызвать их в правильном порядке и с правильными пропсами и состоянием. Давайте посмотрим, как именно это происходит. -### How React re-synchronizes your Effect {/*how-react-re-synchronizes-your-effect*/} +### Как React повторно синхронизирует ваш эффект {/*how-react-re-synchronizes-your-effect*/} -Recall that your `ChatRoom` component has received a new value for its `roomId` prop. It used to be `"general"`, and now it is `"travel"`. React needs to re-synchronize your Effect to re-connect you to a different room. +Помните, что ваш компонент `ChatRoom` получил новое значение для своего пропса `roomId`. Раньше это было `"general"`, а теперь `"travel"`. React необходимо повторно синхронизировать ваш эффект, чтобы переподключить вас к другой комнате. -To **stop synchronizing,** React will call the cleanup function that your Effect returned after connecting to the `"general"` room. Since `roomId` was `"general"`, the cleanup function disconnects from the `"general"` room: +Чтобы **прекратить синхронизацию,** React вызовет функцию очистки, которую ваш эффект вернул после подключения к комнате `"general"`. Поскольку `roomId` был `"general"`, функция очистки отключается от комнаты `"general"`: ```js {6} function ChatRoom({ roomId /* "general" */ }) { useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Connects to the "general" room + const connection = createConnection(serverUrl, roomId); // Подключается к комнате "general" connection.connect(); return () => { - connection.disconnect(); // Disconnects from the "general" room + connection.disconnect(); // Отключается от комнаты "general" }; // ... ``` -Then React will run the Effect that you've provided during this render. This time, `roomId` is `"travel"` so it will **start synchronizing** to the `"travel"` chat room (until its cleanup function is eventually called too): +Затем React запустит эффект, который вы предоставили во время этого рендеринга. На этот раз `roomId` будет `"travel"`, поэтому он **начнет синхронизацию** с чат-комнатой `"travel"` (до тех пор, пока его функция очистки не будет вызвана снова). ```js {3,4} function ChatRoom({ roomId /* "travel" */ }) { useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Connects to the "travel" room + const connection = createConnection(serverUrl, roomId); // Подключается к комнате "travel" connection.connect(); // ... ``` -Thanks to this, you're now connected to the same room that the user chose in the UI. Disaster averted! +Благодаря этому вы теперь подключены к той же комнате, которую выбрал пользователь в пользовательском интерфейсе. Катастрофа предотвращена! -Every time after your component re-renders with a different `roomId`, your Effect will re-synchronize. For example, let's say the user changes `roomId` from `"travel"` to `"music"`. React will again **stop synchronizing** your Effect by calling its cleanup function (disconnecting you from the `"travel"` room). Then it will **start synchronizing** again by running its body with the new `roomId` prop (connecting you to the `"music"` room). +Каждый раз после повторного рендеринга вашего компонента с измененным `roomId` ваш эффект будет повторно синхронизироваться. Например, скажем, пользователь изменяет `roomId` с `"travel"` на `"music"`. React снова **прекратит синхронизацию** вашего эффекта, вызвав его функцию очистки (отключив вас от комнаты `"travel"`). Затем он снова **начнет синхронизацию**, запустив тело эффекта с новым пропсом `roomId` (подключив вас к комнате `"music"`). -Finally, when the user goes to a different screen, `ChatRoom` unmounts. Now there is no need to stay connected at all. React will **stop synchronizing** your Effect one last time and disconnect you from the `"music"` chat room. +Наконец, когда пользователь переходит на другой экран, `ChatRoom` размонтируется. Теперь нет необходимости оставаться подключенным. React **прекратит синхронизацию** вашего эффекта в последний раз и отключит вас от чат-комнаты `"music"`. -### Thinking from the Effect's perspective {/*thinking-from-the-effects-perspective*/} +### Мышление с точки зрения эффекта {/*thinking-from-the-effects-perspective*/} -Let's recap everything that's happened from the `ChatRoom` component's perspective: +Подведем итог всему, что произошло с точки зрения компонента `ChatRoom`: -1. `ChatRoom` mounted with `roomId` set to `"general"` -1. `ChatRoom` updated with `roomId` set to `"travel"` -1. `ChatRoom` updated with `roomId` set to `"music"` -1. `ChatRoom` unmounted +1. `ChatRoom` смонтировался с `roomId`, установленным в `"general"` +1. `ChatRoom` обновился с `roomId`, установленным в `"travel"` +1. `ChatRoom` обновился с `roomId`, установленным в `"music"` +1. `ChatRoom` размонтировался -During each of these points in the component's lifecycle, your Effect did different things: +Во время каждой из этих точек жизненного цикла вашего компонента ваш эффект делал разные вещи: -1. Your Effect connected to the `"general"` room -1. Your Effect disconnected from the `"general"` room and connected to the `"travel"` room -1. Your Effect disconnected from the `"travel"` room and connected to the `"music"` room -1. Your Effect disconnected from the `"music"` room +1. Ваш эффект подключился к комнате `"general"` +1. Ваш эффект отключился от комнаты `"general"` и подключился к комнате `"travel"` +1. Ваш эффект отключился от комнаты `"travel"` и подключился к комнате `"music"` +1. Ваш эффект отключился от комнаты `"music"` -Now let's think about what happened from the perspective of the Effect itself: +Теперь давайте подумаем о том, что произошло с точки зрения самого эффекта: ```js useEffect(() => { - // Your Effect connected to the room specified with roomId... + // Ваш эффект подключился к комнате, указанной в roomId... const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { - // ...until it disconnected + // ...пока не отключился connection.disconnect(); }; }, [roomId]); ``` -This code's structure might inspire you to see what happened as a sequence of non-overlapping time periods: +Структура этого кода может вдохновить вас увидеть, что произошло, как последовательность перекрывающихся периодов времени: -1. Your Effect connected to the `"general"` room (until it disconnected) -1. Your Effect connected to the `"travel"` room (until it disconnected) -1. Your Effect connected to the `"music"` room (until it disconnected) +1. Ваш эффект подключился к комнате `"general"` (пока не отключился) +1. Ваш эффект подключился к комнате `"travel"` (пока не отключился) +1. Ваш эффект подключился к комнате `"music"` (пока не отключился) -Previously, you were thinking from the component's perspective. When you looked from the component's perspective, it was tempting to think of Effects as "callbacks" or "lifecycle events" that fire at a specific time like "after a render" or "before unmount". This way of thinking gets complicated very fast, so it's best to avoid. +Ранее вы думали с точки зрения компонента. Когда вы смотрели с точки зрения компонента, было заманчиво думать об эффектах как о "колбэках" или "событиях жизненного цикла", которые срабатывают в определенное время, например "после рендеринга" или "перед размонтированием". Такой способ мышления очень быстро усложняется, поэтому его лучше избегать. -**Instead, always focus on a single start/stop cycle at a time. It shouldn't matter whether a component is mounting, updating, or unmounting. All you need to do is to describe how to start synchronization and how to stop it. If you do it well, your Effect will be resilient to being started and stopped as many times as it's needed.** +**Вместо этого всегда фокусируйтесь на одном цикле запуска/остановки за раз. Не должно иметь значения, монтируется, обновляется или размонтируется компонент. Все, что вам нужно сделать, — это описать, как начать синхронизацию и как ее прекратить. Если вы сделаете это хорошо, ваш эффект будет устойчив к запуску и остановке столько раз, сколько потребуется.** -This might remind you how you don't think whether a component is mounting or updating when you write the rendering logic that creates JSX. You describe what should be on the screen, and React [figures out the rest.](/learn/reacting-to-input-with-state) +Это может напомнить вам, как вы не задумываетесь, монтируется или обновляется компонент, когда пишете логику рендеринга, создающую JSX. Вы описываете, что должно быть на экране, а React [сам все выясняет.](/learn/reacting-to-input-with-state) -### How React verifies that your Effect can re-synchronize {/*how-react-verifies-that-your-effect-can-re-synchronize*/} +### Как React проверяет, что ваш эффект может повторно синхронизироваться {/*how-react-verifies-that-your-effect-can-re-synchronize*/} -Here is a live example that you can play with. Press "Open chat" to mount the `ChatRoom` component: +Вот живой пример, с которым вы можете поэкспериментировать. Нажмите "Open chat", чтобы смонтировать компонент `ChatRoom`: @@ -272,49 +272,49 @@ button { margin-left: 10px; } -Notice that when the component mounts for the first time, you see three logs: +Обратите внимание, что при первом монтировании компонента вы увидите три лога: -1. `✅ Connecting to "general" room at https://localhost:1234...` *(development-only)* -1. `❌ Disconnected from "general" room at https://localhost:1234.` *(development-only)* +1. `✅ Connecting to "general" room at https://localhost:1234...` *(только в режиме разработки)* +1. `❌ Disconnected from "general" room at https://localhost:1234.` *(только в режиме разработки)* 1. `✅ Connecting to "general" room at https://localhost:1234...` -The first two logs are development-only. In development, React always remounts each component once. +Первые два лога — это только для режима разработки. В режиме разработки React всегда повторно монтирует каждый компонент один раз. -**React verifies that your Effect can re-synchronize by forcing it to do that immediately in development.** This might remind you of opening a door and closing it an extra time to check if the door lock works. React starts and stops your Effect one extra time in development to check [you've implemented its cleanup well.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) +**React проверяет, что ваш эффект может повторно синхронизироваться, заставляя его сделать это немедленно в режиме разработки.** Это может напомнить вам, как вы открываете дверь и закрываете ее еще раз, чтобы проверить, работает ли замок. React запускает и останавливает ваш эффект один раз дополнительно в режиме разработки, чтобы проверить, [хорошо ли вы реализовали его очистку.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) -The main reason your Effect will re-synchronize in practice is if some data it uses has changed. In the sandbox above, change the selected chat room. Notice how, when the `roomId` changes, your Effect re-synchronizes. +Основная причина, по которой ваш эффект будет повторно синхронизироваться на практике, заключается в изменении некоторых используемых им данных. В песочнице выше измените выбранную комнату чата. Обратите внимание, как при изменении `roomId` ваш эффект повторно синхронизируется. -However, there are also more unusual cases in which re-synchronization is necessary. For example, try editing the `serverUrl` in the sandbox above while the chat is open. Notice how the Effect re-synchronizes in response to your edits to the code. In the future, React may add more features that rely on re-synchronization. +Однако существуют и более необычные случаи, когда повторная синхронизация необходима. Например, попробуйте отредактировать `serverUrl` в песочнице выше, пока чат открыт. Обратите внимание, как эффект повторно синхронизируется в ответ на ваши правки кода. В будущем React может добавить новые функции, которые полагаются на повторную синхронизацию. -### How React knows that it needs to re-synchronize the Effect {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} +### Как React узнает, что эффект нужно повторно синхронизировать {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} -You might be wondering how React knew that your Effect needed to re-synchronize after `roomId` changes. It's because *you told React* that its code depends on `roomId` by including it in the [list of dependencies:](/learn/synchronizing-with-effects#step-2-specify-the-effect-dependencies) +Возможно, вы задаетесь вопросом, как React узнал, что ваш эффект нуждается в повторной синхронизации после изменения `roomId`. Это потому, что *вы сказали React*, что его код зависит от `roomId`, включив его в [список зависимостей:](/learn/synchronizing-with-effects#step-2-specify-the-effect-dependencies) ```js {1,3,8} -function ChatRoom({ roomId }) { // The roomId prop may change over time +function ChatRoom({ roomId }) { // Пропс roomId может меняться со временем useEffect(() => { - const connection = createConnection(serverUrl, roomId); // This Effect reads roomId + const connection = createConnection(serverUrl, roomId); // Этот эффект читает roomId connection.connect(); return () => { connection.disconnect(); }; - }, [roomId]); // So you tell React that this Effect "depends on" roomId + }, [roomId]); // Поэтому вы говорите React, что этот эффект "зависит от" roomId // ... ``` -Here's how this works: +Вот как это работает: -1. You knew `roomId` is a prop, which means it can change over time. -2. You knew that your Effect reads `roomId` (so its logic depends on a value that may change later). -3. This is why you specified it as your Effect's dependency (so that it re-synchronizes when `roomId` changes). +1. Вы знали, что `roomId` — это пропс, что означает, что он может меняться со временем. +2. Вы знали, что ваш эффект читает `roomId` (следовательно, его логика зависит от значения, которое может измениться позже). +3. Вот почему вы указали его как зависимость вашего эффекта (чтобы он повторно синхронизировался при изменении `roomId`). -Every time after your component re-renders, React will look at the array of dependencies that you have passed. If any of the values in the array is different from the value at the same spot that you passed during the previous render, React will re-synchronize your Effect. +Каждый раз после повторного рендеринга вашего компонента React будет смотреть на массив зависимостей, который вы передали. Если какое-либо из значений в массиве отличается от значения в том же месте, которое вы передали во время предыдущего рендеринга, React повторно синхронизирует ваш эффект. -For example, if you passed `["general"]` during the initial render, and later you passed `["travel"]` during the next render, React will compare `"general"` and `"travel"`. These are different values (compared with [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), so React will re-synchronize your Effect. On the other hand, if your component re-renders but `roomId` has not changed, your Effect will remain connected to the same room. +Например, если вы передали `["general"]` во время начального рендеринга, а затем позже передали `["travel"]` во время следующего рендеринга, React сравнит `"general"` и `"travel"`. Это разные значения (сравненные с помощью [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), поэтому React повторно синхронизирует ваш эффект. С другой стороны, если ваш компонент повторно рендерится, но `roomId` не изменился, ваш эффект останется подключенным к той же комнате. -### Each Effect represents a separate synchronization process {/*each-effect-represents-a-separate-synchronization-process*/} +### Каждый эффект представляет отдельный процесс синхронизации {/*each-effect-represents-a-separate-synchronization-process*/} -Resist adding unrelated logic to your Effect only because this logic needs to run at the same time as an Effect you already wrote. For example, let's say you want to send an analytics event when the user visits the room. You already have an Effect that depends on `roomId`, so you might feel tempted to add the analytics call there: +Старайтесь не добавлять в ваш эффект несвязанную логику только потому, что эта логика должна выполняться одновременно с уже написанным вами эффектом. Например, предположим, вы хотите отправить событие аналитики, когда пользователь посещает комнату. У вас уже есть эффект, зависящий от `roomId`, поэтому вы можете почувствовать искушение добавить туда вызов аналитики: ```js {3} function ChatRoom({ roomId }) { @@ -330,7 +330,7 @@ function ChatRoom({ roomId }) { } ``` -But imagine you later add another dependency to this Effect that needs to re-establish the connection. If this Effect re-synchronizes, it will also call `logVisit(roomId)` for the same room, which you did not intend. Logging the visit **is a separate process** from connecting. Write them as two separate Effects: +Но представьте, что позже вы добавите другую зависимость к этому эффекту, которая потребует переустановки соединения. Если этот эффект будет повторно синхронизироваться, он также вызовет `logVisit(roomId)` для той же комнаты, чего вы не предполагали. Логирование посещения — это **отдельный процесс** от подключения. Напишите их как два отдельных эффекта: ```js {2-4} function ChatRoom({ roomId }) { @@ -346,13 +346,13 @@ function ChatRoom({ roomId }) { } ``` -**Each Effect in your code should represent a separate and independent synchronization process.** +**Каждый эффект в вашем коде должен представлять отдельный и независимый процесс синхронизации.** -In the above example, deleting one Effect wouldn’t break the other Effect's logic. This is a good indication that they synchronize different things, and so it made sense to split them up. On the other hand, if you split up a cohesive piece of logic into separate Effects, the code may look "cleaner" but will be [more difficult to maintain.](/learn/you-might-not-need-an-effect#chains-of-computations) This is why you should think whether the processes are same or separate, not whether the code looks cleaner. +В приведенном выше примере удаление одного эффекта не нарушит логику другого. Это хороший признак того, что они синхронизируют разные вещи, и поэтому их имело смысл разделить. С другой стороны, если вы разделите единый блок логики на отдельные эффекты, код может выглядеть «чище», но его будет [сложнее поддерживать.](/learn/you-might-not-need-an-effect#chains-of-computations) Вот почему вы должны думать, являются ли процессы одинаковыми или отдельными, а не о том, выглядит ли код чище. -## Effects "react" to reactive values {/*effects-react-to-reactive-values*/} +## Эффекты "реагируют" на реактивные значения {/*effects-react-to-reactive-values*/} -Your Effect reads two variables (`serverUrl` and `roomId`), but you only specified `roomId` as a dependency: +Ваш эффект читает две переменные (`serverUrl` и `roomId`), но вы указали только `roomId` в качестве зависимости: ```js {5,10} const serverUrl = 'https://localhost:1234'; @@ -369,32 +369,32 @@ function ChatRoom({ roomId }) { } ``` -Why doesn't `serverUrl` need to be a dependency? +Почему `serverUrl` не нужно указывать как зависимость? -This is because the `serverUrl` never changes due to a re-render. It's always the same no matter how many times the component re-renders and why. Since `serverUrl` never changes, it wouldn't make sense to specify it as a dependency. After all, dependencies only do something when they change over time! +Это связано с тем, что `serverUrl` никогда не меняется из-за повторного рендеринга. Он всегда одинаков, независимо от того, сколько раз компонент перерисовывается и почему. Поскольку `serverUrl` никогда не меняется, указывать его как зависимость не имеет смысла. В конце концов, зависимости делают что-то только тогда, когда они меняются со временем! -On the other hand, `roomId` may be different on a re-render. **Props, state, and other values declared inside the component are _reactive_ because they're calculated during rendering and participate in the React data flow.** +С другой стороны, `roomId` может быть другим при повторном рендеринге. **Пропсы, состояние и другие значения, объявленные внутри компонента, являются _реактивными_, потому что они вычисляются во время рендеринга и участвуют в потоке данных React.** -If `serverUrl` was a state variable, it would be reactive. Reactive values must be included in dependencies: +Если бы `serverUrl` был переменной состояния, он был бы реактивным. Реактивные значения должны быть включены в зависимости: ```js {2,5,10} -function ChatRoom({ roomId }) { // Props change over time - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // State may change over time +function ChatRoom({ roomId }) { // Пропсы меняются со временем + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // Состояние может меняться со временем useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Your Effect reads props and state + const connection = createConnection(serverUrl, roomId); // Ваш эффект читает пропсы и состояние connection.connect(); return () => { connection.disconnect(); }; - }, [roomId, serverUrl]); // So you tell React that this Effect "depends on" on props and state + }, [roomId, serverUrl]); // Поэтому вы сообщаете React, что этот эффект "зависит от" пропсов и состояния // ... } ``` -By including `serverUrl` as a dependency, you ensure that the Effect re-synchronizes after it changes. +Включив `serverUrl` в зависимости, вы гарантируете, что эффект будет повторно синхронизироваться после его изменения. -Try changing the selected chat room or edit the server URL in this sandbox: +Попробуйте изменить выбранную комнату чата или отредактировать URL сервера в этой песочнице: @@ -468,11 +468,11 @@ button { margin-left: 10px; } -Whenever you change a reactive value like `roomId` or `serverUrl`, the Effect re-connects to the chat server. +Всякий раз, когда вы изменяете реактивное значение, такое как `roomId` или `serverUrl`, эффект переподключается к серверу чата. -### What an Effect with empty dependencies means {/*what-an-effect-with-empty-dependencies-means*/} +### Что означает эффект с пустыми зависимостями {/*what-an-effect-with-empty-dependencies-means*/} -What happens if you move both `serverUrl` and `roomId` outside the component? +Что произойдет, если вы вынесете `serverUrl` и `roomId` за пределы компонента? ```js {1,2} const serverUrl = 'https://localhost:1234'; @@ -485,14 +485,14 @@ function ChatRoom() { return () => { connection.disconnect(); }; - }, []); // ✅ All dependencies declared + }, []); // ✅ Все зависимости объявлены // ... } ``` -Now your Effect's code does not use *any* reactive values, so its dependencies can be empty (`[]`). +Теперь код вашего эффекта не использует *никаких* реактивных значений, поэтому его зависимости могут быть пустыми (`[]`). -Thinking from the component's perspective, the empty `[]` dependency array means this Effect connects to the chat room only when the component mounts, and disconnects only when the component unmounts. (Keep in mind that React would still [re-synchronize it an extra time](#how-react-verifies-that-your-effect-can-re-synchronize) in development to stress-test your logic.) +С точки зрения компонента, пустой массив зависимостей `[]` означает, что эффект подключается к комнате чата только при монтировании компонента и отключается только при размонтировании компонента. (Имейте в виду, что React все равно [повторно синхронизирует его один раз дополнительно](#how-react-verifies-that-your-effect-can-re-synchronize) в режиме разработки, чтобы протестировать вашу логику.) @@ -548,52 +548,52 @@ button { margin-left: 10px; } -However, if you [think from the Effect's perspective,](#thinking-from-the-effects-perspective) you don't need to think about mounting and unmounting at all. What's important is you've specified what your Effect does to start and stop synchronizing. Today, it has no reactive dependencies. But if you ever want the user to change `roomId` or `serverUrl` over time (and they would become reactive), your Effect's code won't change. You will only need to add them to the dependencies. +Однако, если вы [думаете с точки зрения эффекта](#thinking-from-the-effects-perspective), вам вообще не нужно думать о монтировании и размонтировании. Важно то, что вы указали, что делает ваш эффект для начала и прекращения синхронизации. Сегодня у него нет реактивных зависимостей. Но если вы когда-нибудь захотите, чтобы пользователь со временем изменял `roomId` или `serverUrl` (и они станут реактивными), код вашего эффекта не изменится. Вам нужно будет только добавить их в зависимости. -### All variables declared in the component body are reactive {/*all-variables-declared-in-the-component-body-are-reactive*/} +### Все переменные, объявленные в теле компонента, реактивны {/*all-variables-declared-in-the-component-body-are-reactive*/} -Props and state aren't the only reactive values. Values that you calculate from them are also reactive. If the props or state change, your component will re-render, and the values calculated from them will also change. This is why all variables from the component body used by the Effect should be in the Effect dependency list. +Пропсы и состояние — это не единственные реактивные значения. Значения, которые вы вычисляете из них, также реактивны. Если пропсы или состояние изменятся, ваш компонент перерисуется, и значения, вычисленные из них, также изменятся. Вот почему все переменные из тела компонента, используемые эффектом, должны быть в списке зависимостей эффекта. -Let's say that the user can pick a chat server in the dropdown, but they can also configure a default server in settings. Suppose you've already put the settings state in a [context](/learn/scaling-up-with-reducer-and-context) so you read the `settings` from that context. Now you calculate the `serverUrl` based on the selected server from props and the default server: +Допустим, пользователь может выбрать сервер чата в выпадающем списке, но также может настроить сервер по умолчанию в настройках. Предположим, вы уже поместили состояние настроек в [контекст](/learn/scaling-up-with-reducer-and-context), поэтому вы читаете `settings` из этого контекста. Теперь вы вычисляете `serverUrl` на основе выбранного сервера из пропсов и сервера по умолчанию: ```js {3,5,10} -function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive - const settings = useContext(SettingsContext); // settings is reactive - const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl is reactive +function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен + const settings = useContext(SettingsContext); // settings реактивен + const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl реактивен useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Your Effect reads roomId and serverUrl + const connection = createConnection(serverUrl, roomId); // Ваш эффект читает roomId и serverUrl connection.connect(); return () => { connection.disconnect(); }; - }, [roomId, serverUrl]); // So it needs to re-synchronize when either of them changes! + }, [roomId, serverUrl]); // Поэтому ему нужно повторно синхронизироваться при изменении любого из них! // ... } ``` -In this example, `serverUrl` is not a prop or a state variable. It's a regular variable that you calculate during rendering. But it's calculated during rendering, so it can change due to a re-render. This is why it's reactive. +В этом примере `serverUrl` не является пропсом или переменной состояния. Это обычная переменная, которую вы вычисляете во время рендеринга. Но она вычисляется во время рендеринга, поэтому может измениться из-за повторного рендеринга. Вот почему она реактивна. -**All values inside the component (including props, state, and variables in your component's body) are reactive. Any reactive value can change on a re-render, so you need to include reactive values as Effect's dependencies.** +**Все значения внутри компонента (включая пропсы, состояние и переменные в теле вашего компонента) реактивны. Любое реактивное значение может измениться при повторном рендеринге, поэтому вам нужно включить реактивные значения в качестве зависимостей эффекта.** -In other words, Effects "react" to all values from the component body. +Другими словами, эффекты "реагируют" на все значения из тела компонента. -#### Can global or mutable values be dependencies? {/*can-global-or-mutable-values-be-dependencies*/} +#### Могут ли глобальные или изменяемые значения быть зависимостями? {/*can-global-or-mutable-values-be-dependencies*/} -Mutable values (including global variables) aren't reactive. +Изменяемые значения (включая глобальные переменные) не реактивны. -**A mutable value like [`location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname) can't be a dependency.** It's mutable, so it can change at any time completely outside of the React rendering data flow. Changing it wouldn't trigger a re-render of your component. Therefore, even if you specified it in the dependencies, React *wouldn't know* to re-synchronize the Effect when it changes. This also breaks the rules of React because reading mutable data during rendering (which is when you calculate the dependencies) breaks [purity of rendering.](/learn/keeping-components-pure) Instead, you should read and subscribe to an external mutable value with [`useSyncExternalStore`.](/learn/you-might-not-need-an-effect#subscribing-to-an-external-store) +**Изменяемое значение, такое как [`location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname), не может быть зависимостью.** Оно изменяемо, поэтому может измениться в любой момент совершенно вне потока данных рендеринга React. Его изменение не вызовет повторный рендеринг вашего компонента. Следовательно, даже если вы укажете его в зависимостях, React *не узнает*, чтобы повторно синхронизировать эффект при его изменении. Это также нарушает правила React, потому что чтение изменяемых данных во время рендеринга (когда вы вычисляете зависимости) нарушает [чистоту рендеринга.](/learn/keeping-components-pure) Вместо этого вы должны читать и подписываться на внешний изменяемый объект с помощью [`useSyncExternalStore`.](/learn/you-might-not-need-an-effect#subscribing-to-an-external-store) -**A mutable value like [`ref.current`](/reference/react/useRef#reference) or things you read from it also can't be a dependency.** The ref object returned by `useRef` itself can be a dependency, but its `current` property is intentionally mutable. It lets you [keep track of something without triggering a re-render.](/learn/referencing-values-with-refs) But since changing it doesn't trigger a re-render, it's not a reactive value, and React won't know to re-run your Effect when it changes. +**Изменяемое значение, такое как [`ref.current`](/reference/react/useRef#reference) или то, что вы из него читаете, также не может быть зависимостью.** Сам объект ref, возвращаемый `useRef`, может быть зависимостью, но его свойство `current` намеренно изменяемо. Оно позволяет вам [отслеживать что-то, не вызывая повторный рендеринг.](/learn/referencing-values-with-refs) Но поскольку его изменение не вызывает повторный рендеринг, это не реактивное значение, и React не узнает, чтобы повторно запустить ваш эффект при его изменении. -As you'll learn below on this page, a linter will check for these issues automatically. +Как вы узнаете ниже на этой странице, линтер автоматически проверит эти проблемы. -### React verifies that you specified every reactive value as a dependency {/*react-verifies-that-you-specified-every-reactive-value-as-a-dependency*/} +### React проверяет, что вы указали каждую реактивную переменную в качестве зависимости {/*react-verifies-that-you-specified-every-reactive-value-as-a-dependency*/} -If your linter is [configured for React,](/learn/editor-setup#linting) it will check that every reactive value used by your Effect's code is declared as its dependency. For example, this is a lint error because both `roomId` and `serverUrl` are reactive: +Если ваш линтер [настроен для React,](/learn/editor-setup#linting) он проверит, что каждое реактивное значение, используемое кодом вашего эффекта, объявлено как его зависимость. Например, это ошибка линтера, потому что и `roomId`, и `serverUrl` реактивны: @@ -601,14 +601,14 @@ If your linter is [configured for React,](/learn/editor-setup#linting) it will c import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; -function ChatRoom({ roomId }) { // roomId is reactive - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive +function ChatRoom({ roomId }) { // roomId реактивен + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl реактивен useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); - }, []); // <-- Something's wrong here! + }, []); // <-- Здесь что-то не так! return ( <> @@ -667,41 +667,41 @@ button { margin-left: 10px; } -This may look like a React error, but really React is pointing out a bug in your code. Both `roomId` and `serverUrl` may change over time, but you're forgetting to re-synchronize your Effect when they change. You will remain connected to the initial `roomId` and `serverUrl` even after the user picks different values in the UI. +Это может выглядеть как ошибка React, но на самом деле React указывает на ошибку в вашем коде. И `roomId`, и `serverUrl` могут меняться со временем, но вы забыли повторно синхронизировать ваш эффект при их изменении. Вы останетесь подключены к начальным `roomId` и `serverUrl` даже после того, как пользователь выберет другие значения в пользовательском интерфейсе. -To fix the bug, follow the linter's suggestion to specify `roomId` and `serverUrl` as dependencies of your Effect: +Чтобы исправить ошибку, следуйте предложению линтера указать `roomId` и `serverUrl` в качестве зависимостей вашего эффекта: ```js {9} -function ChatRoom({ roomId }) { // roomId is reactive - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive +function ChatRoom({ roomId }) { // roomId реактивен + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl реактивен useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; - }, [serverUrl, roomId]); // ✅ All dependencies declared + }, [serverUrl, roomId]); // ✅ Все зависимости объявлены // ... } ``` -Try this fix in the sandbox above. Verify that the linter error is gone, and the chat re-connects when needed. +Попробуйте это исправление в песочнице выше. Убедитесь, что ошибка линтера исчезла, и чат переподключается при необходимости. -In some cases, React *knows* that a value never changes even though it's declared inside the component. For example, the [`set` function](/reference/react/useState#setstate) returned from `useState` and the ref object returned by [`useRef`](/reference/react/useRef) are *stable*--they are guaranteed to not change on a re-render. Stable values aren't reactive, so you may omit them from the list. Including them is allowed: they won't change, so it doesn't matter. +В некоторых случаях React *знает*, что значение никогда не меняется, даже если оно объявлено внутри компонента. Например, функция [`set` (установки)](/reference/react/useState#setstate), возвращаемая из `useState`, и объект ref, возвращаемый [`useRef`](/reference/react/useRef), являются *стабильными* — гарантируется, что они не изменятся при повторном рендеринге. Стабильные значения не реактивны, поэтому вы можете опустить их из списка. Включение их разрешено: они не изменятся, так что это не имеет значения. -### What to do when you don't want to re-synchronize {/*what-to-do-when-you-dont-want-to-re-synchronize*/} +### Что делать, если вы не хотите повторно синхронизировать {/*what-to-do-when-you-dont-want-to-re-synchronize*/} -In the previous example, you've fixed the lint error by listing `roomId` and `serverUrl` as dependencies. +В предыдущем примере вы исправили ошибку линтера, перечислив `roomId` и `serverUrl` в качестве зависимостей. -**However, you could instead "prove" to the linter that these values aren't reactive values,** i.e. that they *can't* change as a result of a re-render. For example, if `serverUrl` and `roomId` don't depend on rendering and always have the same values, you can move them outside the component. Now they don't need to be dependencies: +**Однако вы могли бы вместо этого «доказать» линтеру, что эти значения не являются реактивными,** то есть они *не могут* измениться в результате повторного рендеринга. Например, если `serverUrl` и `roomId` не зависят от рендеринга и всегда имеют одинаковые значения, вы можете вынести их за пределы компонента. Теперь они не нуждаются в зависимостях: ```js {1,2,11} -const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive -const roomId = 'general'; // roomId is not reactive +const serverUrl = 'https://localhost:1234'; // serverUrl не является реактивным +const roomId = 'general'; // roomId не является реактивным function ChatRoom() { useEffect(() => { @@ -710,80 +710,80 @@ function ChatRoom() { return () => { connection.disconnect(); }; - }, []); // ✅ All dependencies declared + }, []); // ✅ Все зависимости объявлены // ... } ``` -You can also move them *inside the Effect.* They aren't calculated during rendering, so they're not reactive: +Вы также можете вынести их *внутрь эффекта*. Они не вычисляются во время рендеринга, поэтому не являются реактивными: ```js {3,4,10} function ChatRoom() { useEffect(() => { - const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive - const roomId = 'general'; // roomId is not reactive + const serverUrl = 'https://localhost:1234'; // serverUrl не является реактивным + const roomId = 'general'; // roomId не является реактивным const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; - }, []); // ✅ All dependencies declared + }, []); // ✅ Все зависимости объявлены // ... } ``` -**Effects are reactive blocks of code.** They re-synchronize when the values you read inside of them change. Unlike event handlers, which only run once per interaction, Effects run whenever synchronization is necessary. +**Эффекты — это реактивные блоки кода.** Они повторно синхронизируются при изменении значений, которые вы читаете внутри них. В отличие от обработчиков событий, которые выполняются только один раз за взаимодействие, эффекты выполняются всякий раз, когда требуется синхронизация. -**You can't "choose" your dependencies.** Your dependencies must include every [reactive value](#all-variables-declared-in-the-component-body-are-reactive) you read in the Effect. The linter enforces this. Sometimes this may lead to problems like infinite loops and to your Effect re-synchronizing too often. Don't fix these problems by suppressing the linter! Here's what to try instead: +**Вы не можете «выбирать» свои зависимости.** Ваши зависимости должны включать каждое [реактивное значение](#all-variables-declared-in-the-component-body-are-reactive), которое вы читаете в эффекте. Линтер обеспечивает это. Иногда это может привести к проблемам, таким как бесконечные циклы, и к тому, что ваш эффект будет повторно синхронизироваться слишком часто. Не исправляйте эти проблемы, подавляя линтер! Вот что стоит попробовать вместо этого: -* **Check that your Effect represents an independent synchronization process.** If your Effect doesn't synchronize anything, [it might be unnecessary.](/learn/you-might-not-need-an-effect) If it synchronizes several independent things, [split it up.](#each-effect-represents-a-separate-synchronization-process) +* **Проверьте, представляет ли ваш эффект независимый процесс синхронизации.** Если ваш эффект ничего не синхронизирует, [он может быть ненужным.](/learn/you-might-not-need-an-effect) Если он синхронизирует несколько независимых вещей, [разделите его.](#each-effect-represents-a-separate-synchronization-process) -* **If you want to read the latest value of props or state without "reacting" to it and re-synchronizing the Effect,** you can split your Effect into a reactive part (which you'll keep in the Effect) and a non-reactive part (which you'll extract into something called an _Effect Event_). [Read about separating Events from Effects.](/learn/separating-events-from-effects) +* **Если вы хотите прочитать последнее значение пропсов или состояния, не «реагируя» на него и не повторно синхронизируя эффект,** вы можете разделить ваш эффект на реактивную часть (которую вы оставите в эффекте) и нереактивную часть (которую вы вынесете во что-то под названием _Событие эффекта_). [Читайте о разделении событий и эффектов.](/learn/separating-events-from-effects) -* **Avoid relying on objects and functions as dependencies.** If you create objects and functions during rendering and then read them from an Effect, they will be different on every render. This will cause your Effect to re-synchronize every time. [Read more about removing unnecessary dependencies from Effects.](/learn/removing-effect-dependencies) +* **Избегайте использования объектов и функций в качестве зависимостей.** Если вы создаете объекты и функции во время рендеринга, а затем читаете их из эффекта, они будут отличаться при каждом рендеринге. Это приведет к повторной синхронизации эффекта каждый раз. [Узнайте больше об удалении ненужных зависимостей из эффектов.](/learn/removing-effect-dependencies) -The linter is your friend, but its powers are limited. The linter only knows when the dependencies are *wrong*. It doesn't know *the best* way to solve each case. If the linter suggests a dependency, but adding it causes a loop, it doesn't mean the linter should be ignored. You need to change the code inside (or outside) the Effect so that that value isn't reactive and doesn't *need* to be a dependency. +Линтер — ваш друг, но его возможности ограничены. Линтер знает только, когда зависимости *неправильные*. Он не знает *лучшего* способа решения каждого случая. Если линтер предлагает зависимость, но ее добавление вызывает цикл, это не значит, что линтер следует игнорировать. Вам нужно изменить код внутри (или вне) эффекта так, чтобы это значение не было реактивным и не *требовало* быть зависимостью. -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 }, []); ``` -On the [next](/learn/separating-events-from-effects) [pages](/learn/removing-effect-dependencies), you'll learn how to fix this code without breaking the rules. It's always worth fixing! +На [следующих](/learn/separating-events-from-effects) [страницах](/learn/removing-effect-dependencies) вы узнаете, как исправить этот код, не нарушая правил. Всегда стоит исправлять! -- Components can mount, update, and unmount. -- Each Effect has a separate lifecycle from the surrounding component. -- Each Effect describes a separate synchronization process that can *start* and *stop*. -- When you write and read Effects, think from each individual Effect's perspective (how to start and stop synchronization) rather than from the component's perspective (how it mounts, updates, or unmounts). -- Values declared inside the component body are "reactive". -- Reactive values should re-synchronize the Effect because they can change over time. -- The linter verifies that all reactive values used inside the Effect are specified as dependencies. -- All errors flagged by the linter are legitimate. There's always a way to fix the code to not break the rules. +- Компоненты могут монтироваться, обновляться и размонтироваться. +- Каждый эффект имеет отдельный жизненный цикл от окружающего компонента. +- Каждый эффект описывает отдельный процесс синхронизации, который может *начинаться* и *заканчиваться*. +- При написании и чтении эффектов думайте с точки зрения каждого отдельного эффекта (как начать и остановить синхронизацию), а не с точки зрения компонента (как он монтируется, обновляется или размонтируется). +- Значения, объявленные внутри тела компонента, являются «реактивными». +- Реактивные значения должны повторно синхронизировать эффект, поскольку они могут меняться со временем. +- Линтер проверяет, что все реактивные значения, используемые внутри эффекта, указаны в качестве зависимостей. +- Все ошибки, отмеченные линтером, являются обоснованными. Всегда есть способ исправить код, чтобы не нарушать правила. -#### Fix reconnecting on every keystroke {/*fix-reconnecting-on-every-keystroke*/} +#### Исправить повторное подключение при каждом нажатии клавиши {/*fix-reconnecting-on-every-keystroke*/} -In this example, the `ChatRoom` component connects to the chat room when the component mounts, disconnects when it unmounts, and reconnects when you select a different chat room. This behavior is correct, so you need to keep it working. +В этом примере компонент `ChatRoom` подключается к чат-комнате при монтировании компонента, отключается при размонтировании и переподключается при выборе другой чат-комнаты. Это поведение корректно, поэтому вам нужно сохранить его работоспособность. -However, there is a problem. Whenever you type into the message box input at the bottom, `ChatRoom` *also* reconnects to the chat. (You can notice this by clearing the console and typing into the input.) Fix the issue so that this doesn't happen. +Однако есть проблема. Каждый раз, когда вы печатаете в поле ввода сообщения внизу, `ChatRoom` *также* переподключается к чату. (Вы можете заметить это, очистив консоль и напечатав что-нибудь во входном поле.) Исправьте проблему так, чтобы этого не происходило. -You might need to add a dependency array for this Effect. What dependencies should be there? +Возможно, вам потребуется добавить массив зависимостей для этого эффекта. Какие зависимости там должны быть? @@ -860,7 +860,7 @@ button { margin-left: 10px; } -This Effect didn't have a dependency array at all, so it re-synchronized after every re-render. First, add a dependency array. Then, make sure that every reactive value used by the Effect is specified in the array. For example, `roomId` is reactive (because it's a prop), so it should be included in the array. This ensures that when the user selects a different room, the chat reconnects. On the other hand, `serverUrl` is defined outside the component. This is why it doesn't need to be in the array. +У этого эффекта вообще не было массива зависимостей, поэтому он повторно синхронизировался после каждого рендеринга. Сначала добавьте массив зависимостей. Затем убедитесь, что каждое реактивное значение, используемое эффектом, указано в массиве. Например, `roomId` является реактивным (поскольку это пропс), поэтому он должен быть включен в массив. Это гарантирует, что при выборе пользователем другой комнаты чат переподключится. С другой стороны, `serverUrl` определен вне компонента. Поэтому его не нужно включать в массив. @@ -935,15 +935,15 @@ button { margin-left: 10px; } -#### Switch synchronization on and off {/*switch-synchronization-on-and-off*/} +#### Включение и выключение синхронизации {/*switch-synchronization-on-and-off*/} -In this example, an Effect subscribes to the window [`pointermove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event) event to move a pink dot on the screen. Try hovering over the preview area (or touching the screen if you're on a mobile device), and see how the pink dot follows your movement. +В этом примере эффект подписывается на событие `pointermove` окна, чтобы перемещать розовую точку на экране. Попробуйте навести курсор на область предварительного просмотра (или прикоснуться к экрану, если вы находитесь на мобильном устройстве) и посмотрите, как розовая точка следует за вашим движением. -There is also a checkbox. Ticking the checkbox toggles the `canMove` state variable, but this state variable is not used anywhere in the code. Your task is to change the code so that when `canMove` is `false` (the checkbox is ticked off), the dot should stop moving. After you toggle the checkbox back on (and set `canMove` to `true`), the box should follow the movement again. In other words, whether the dot can move or not should stay synchronized to whether the checkbox is checked. +Также есть флажок. Установка флажка переключает переменную состояния `canMove`, но эта переменная состояния нигде в коде не используется. Ваша задача — изменить код так, чтобы при `canMove` равном `false` (флажок снят) точка переставала двигаться. После того как вы снова установите флажок (и установите `canMove` в `true`), точка снова начнет следовать за движением. Другими словами, возможность перемещения точки должна синхронизироваться с тем, отмечен ли флажок. -You can't declare an Effect conditionally. However, the code inside the Effect can use conditions! +Вы не можете объявлять эффект условно. Однако код внутри эффекта может использовать условия! @@ -1001,7 +1001,7 @@ body { -One solution is to wrap the `setPosition` call into an `if (canMove) { ... }` condition: +Одно из решений — обернуть вызов `setPosition` в условие `if (canMove) { ... }`: @@ -1057,7 +1057,7 @@ body { -Alternatively, you could wrap the *event subscription* logic into an `if (canMove) { ... }` condition: +Альтернативно, вы можете обернуть логику *подписки на событие* в условие `if (canMove) { ... }`: @@ -1113,19 +1113,19 @@ body { -In both of these cases, `canMove` is a reactive variable that you read inside the Effect. This is why it must be specified in the list of Effect dependencies. This ensures that the Effect re-synchronizes after every change to its value. +В обоих этих случаях `canMove` является реактивной переменной, которую вы читаете внутри эффекта. Поэтому ее необходимо указать в списке зависимостей эффекта. Это гарантирует, что эффект повторно синхронизируется после каждого изменения ее значения. -#### Investigate a stale value bug {/*investigate-a-stale-value-bug*/} +#### Исследуем проблему устаревшего значения {/*investigate-a-stale-value-bug*/} -In this example, the pink dot should move when the checkbox is on, and should stop moving when the checkbox is off. The logic for this has already been implemented: the `handleMove` event handler checks the `canMove` state variable. +В этом примере розовая точка должна двигаться при включенном флажке и останавливаться при выключенном. Логика для этого уже реализована: обработчик события `handleMove` проверяет переменную состояния `canMove`. -However, for some reason, the `canMove` state variable inside `handleMove` appears to be "stale": it's always `true`, even after you tick off the checkbox. How is this possible? Find the mistake in the code and fix it. +Однако по какой-то причине переменная состояния `canMove` внутри `handleMove` оказывается «устаревшей»: она всегда равна `true`, даже после снятия флажка. Как это возможно? Найдите ошибку в коде и исправьте её. -If you see a linter rule being suppressed, remove the suppression! That's where the mistakes usually are. +Если вы видите подавленное правило линтера, удалите подавление! Обычно ошибки скрываются именно там. @@ -1155,9 +1155,9 @@ export default function App() {
-The problem with the original code was suppressing the dependency linter. If you remove the suppression, you'll see that this Effect depends on the `handleMove` function. This makes sense: `handleMove` is declared inside the component body, which makes it a reactive value. Every reactive value must be specified as a dependency, or it can potentially get stale over time! +Проблема исходного кода заключалась в подавлении линтера зависимостей. Если убрать подавление, вы увидите, что этот эффект зависит от функции `handleMove`. Это имеет смысл: `handleMove` объявлена внутри тела компонента, что делает её реактивным значением. Каждое реактивное значение должно быть указано как зависимость, иначе оно может устареть со временем! -The author of the original code has "lied" to React by saying that the Effect does not depend (`[]`) on any reactive values. This is why React did not re-synchronize the Effect after `canMove` has changed (and `handleMove` with it). Because React did not re-synchronize the Effect, the `handleMove` attached as a listener is the `handleMove` function created during the initial render. During the initial render, `canMove` was `true`, which is why `handleMove` from the initial render will forever see that value. +Автор исходного кода «обманул» React, сказав, что эффект не зависит (`[]`) ни от каких реактивных значений. Именно поэтому React не пересинхронизировал эффект после изменения `canMove` (и вместе с ним `handleMove`). Поскольку React не пересинхронизировал эффект, `handleMove`, добавленный как слушатель, является функцией `handleMove`, созданной во время начального рендеринга. Во время начального рендеринга `canMove` был `true`, поэтому `handleMove` из начального рендеринга навсегда увидит это значение. -**If you never suppress the linter, you will never see problems with stale values.** There are a few different ways to solve this bug, but you should always start by removing the linter suppression. Then change the code to fix the lint error. +**Если вы никогда не будете подавлять линтер, вы никогда не столкнётесь с проблемами устаревших значений.** Существует несколько способов решить эту проблему, но всегда начинайте с удаления подавления линтера. Затем измените код, чтобы исправить ошибку линтера. -You can change the Effect dependencies to `[handleMove]`, but since it's going to be a newly defined function for every render, you might as well remove dependencies array altogether. Then the Effect *will* re-synchronize after every re-render: +Вы можете изменить зависимости эффекта на `[handleMove]`, но поскольку это будет новая функция для каждого рендеринга, вы можете просто убрать массив зависимостей. Тогда эффект *будет* пересинхронизироваться после каждого повторного рендеринга: @@ -1220,9 +1220,9 @@ export default function App() {
-This solution works, but it's not ideal. If you put `console.log('Resubscribing')` inside the Effect, you'll notice that it resubscribes after every re-render. Resubscribing is fast, but it would still be nice to avoid doing it so often. +Это решение работает, но оно не идеально. Если вы добавите `console.log('Resubscribing')` внутрь эффекта, вы заметите, что он повторно подписывается после каждого повторного рендеринга. Повторная подписка — это быстро, но всё же было бы неплохо избегать этого так часто. -A better fix would be to move the `handleMove` function *inside* the Effect. Then `handleMove` won't be a reactive value, and so your Effect won't depend on a function. Instead, it will need to depend on `canMove` which your code now reads from inside the Effect. This matches the behavior you wanted, since your Effect will now stay synchronized with the value of `canMove`: +Лучшим решением будет переместить функцию `handleMove` *внутрь* эффекта. Тогда `handleMove` не будет реактивным значением, и ваш эффект не будет зависеть от функции. Вместо этого он будет зависеть от `canMove`, которое ваш код теперь считывает изнутри эффекта. Это соответствует желаемому поведению, поскольку ваш эффект теперь будет синхронизироваться со значением `canMove`: @@ -1279,9 +1279,9 @@ export default function App() {
-Try adding `console.log('Resubscribing')` inside the Effect body and notice that now it only resubscribes when you toggle the checkbox (`canMove` changes) or edit the code. This makes it better than the previous approach that always resubscribed. +Попробуйте добавить `console.log('Resubscribing')` внутрь тела эффекта и заметьте, что теперь он повторно подписывается только при переключении флажка (`canMove` изменяется) или при редактировании кода. Это делает его лучше предыдущего подхода, который всегда повторно подписывался. -You'll learn a more general approach to this type of problem in [Separating Events from Effects.](/learn/separating-events-from-effects) +Вы узнаете более общий подход к этому типу проблем в разделе [Разделение событий и эффектов.](/learn/separating-events-from-effects) -#### Fix a connection switch {/*fix-a-connection-switch*/} +#### Исправление переключателя соединения {/*fix-a-connection-switch*/} -In this example, the chat service in `chat.js` exposes two different APIs: `createEncryptedConnection` and `createUnencryptedConnection`. The root `App` component lets the user choose whether to use encryption or not, and then passes down the corresponding API method to the child `ChatRoom` component as the `createConnection` prop. +В этом примере сервис чата в `chat.js` предоставляет два разных API: `createEncryptedConnection` и `createUnencryptedConnection`. Корневой компонент `App` позволяет пользователю выбрать, использовать ли шифрование, а затем передает соответствующий метод API дочернему компоненту `ChatRoom` в виде пропса `createConnection`. -Notice that initially, the console logs say the connection is not encrypted. Try toggling the checkbox on: nothing will happen. However, if you change the selected room after that, then the chat will reconnect *and* enable encryption (as you'll see from the console messages). This is a bug. Fix the bug so that toggling the checkbox *also* causes the chat to reconnect. +Обратите внимание, что изначально в консоли отображаются сообщения о том, что соединение не зашифровано. Попробуйте включить флажок: ничего не произойдет. Однако, если после этого вы смените выбранную комнату, чат переподключится *и* включит шифрование (как вы увидите из сообщений в консоли). Это ошибка. Исправьте ошибку так, чтобы переключение флажка *также* вызывало переподключение чата. -Suppressing the linter is always suspicious. Could this be a bug? +Подавление линтера всегда подозрительно. Может ли это быть ошибкой? @@ -1366,7 +1366,7 @@ export default function App() { roomId={roomId} createConnection={isEncrypted ? createEncryptedConnection : - createUnencryptedConnection + createUnconventionalConnection } /> @@ -1423,7 +1423,7 @@ label { display: block; margin-bottom: 10px; } -If you remove the linter suppression, you will see a lint error. The problem is that `createConnection` is a prop, so it's a reactive value. It can change over time! (And indeed, it should--when the user ticks the checkbox, the parent component passes a different value of the `createConnection` prop.) This is why it should be a dependency. Include it in the list to fix the bug: +Если вы уберете подавление линтера, вы увидите ошибку линтера. Проблема в том, что `createConnection` — это пропс, то есть реактивное значение. Оно может меняться со временем! (И действительно, должно — когда пользователь ставит галочку, родительский компонент передает другое значение пропса `createConnection`.) Поэтому оно должно быть зависимостью. Включите его в список, чтобы исправить ошибку: @@ -1518,7 +1518,7 @@ label { display: block; margin-bottom: 10px; } -It is correct that `createConnection` is a dependency. However, this code is a bit fragile because someone could edit the `App` component to pass an inline function as the value of this prop. In that case, its value would be different every time the `App` component re-renders, so the Effect might re-synchronize too often. To avoid this, you can pass `isEncrypted` down instead: +То, что `createConnection` является зависимостью, верно. Однако этот код немного хрупкий, поскольку кто-то может отредактировать компонент `App`, чтобы передать в качестве значения этого пропса инлайн-функцию. В этом случае ее значение будет отличаться при каждом перерисовке компонента `App`, поэтому эффект может слишком часто пересинхронизироваться. Чтобы избежать этого, вы можете передать вместо этого `isEncrypted`: @@ -1613,21 +1613,21 @@ label { display: block; margin-bottom: 10px; } -In this version, the `App` component passes a boolean prop instead of a function. Inside the Effect, you decide which function to use. Since both `createEncryptedConnection` and `createUnencryptedConnection` are declared outside the component, they aren't reactive, and don't need to be dependencies. You'll learn more about this in [Removing Effect Dependencies.](/learn/removing-effect-dependencies) +В этой версии компонент `App` передает булев пропс вместо функции. Внутри эффекта вы решаете, какую функцию использовать. Поскольку `createEncryptedConnection` и `createUnencryptedConnection` объявлены вне компонента, они не являются реактивными и не нуждаются в зависимостях. Вы узнаете больше об этом в разделе [Удаление зависимостей эффекта.](/learn/removing-effect-dependencies) -#### Populate a chain of select boxes {/*populate-a-chain-of-select-boxes*/} +#### Заполнение цепочки выпадающих списков {/*populate-a-chain-of-select-boxes*/} -In this example, there are two select boxes. One select box lets the user pick a planet. Another select box lets the user pick a place *on that planet.* The second box doesn't work yet. Your task is to make it show the places on the chosen planet. +В этом примере есть два выпадающих списка. Один позволяет пользователю выбрать планету. Другой позволяет пользователю выбрать место *на этой планете*. Второй список пока не работает. Ваша задача — добавить код, который будет заполнять его местами на выбранной планете. -Look at how the first select box works. It populates the `planetList` state with the result from the `"/planets"` API call. The currently selected planet's ID is kept in the `planetId` state variable. You need to find where to add some additional code so that the `placeList` state variable is populated with the result of the `"/planets/" + planetId + "/places"` API call. +Посмотрите, как работает первый выпадающий список. Он заполняет состояние `planetList` результатом вызова API `"/planets"`. Идентификатор выбранной планеты хранится в переменной состояния `planetId`. Вам нужно найти место, куда добавить дополнительный код, чтобы переменная состояния `placeList` заполнялась результатом вызова API `"/planets/" + planetId + "/places"`. -If you implement this right, selecting a planet should populate the place list. Changing a planet should change the place list. +Если вы сделаете это правильно, выбор планеты должен заполнить список мест. Изменение планеты должно изменить список мест. -If you have two independent synchronization processes, you need to write two separate Effects. +Если у вас есть два независимых процесса синхронизации, вам нужно написать два отдельных эффекта. @@ -1773,12 +1773,12 @@ label { display: block; margin-bottom: 10px; } -There are two independent synchronization processes: +Существует два независимых процесса синхронизации: -- The first select box is synchronized to the remote list of planets. -- The second select box is synchronized to the remote list of places for the current `planetId`. +- Первый выпадающий список синхронизирован с удаленным списком планет. +- Второй выпадающий список синхронизирован с удаленным списком мест для текущего `planetId`. -This is why it makes sense to describe them as two separate Effects. Here's an example of how you could do this: +Поэтому имеет смысл описывать их как два отдельных эффекта. Вот пример того, как вы можете это сделать: @@ -1809,7 +1809,7 @@ export default function Page() { useEffect(() => { if (planetId === '') { - // Nothing is selected in the first box yet + // Пока ничего не выбрано в первом списке return; } @@ -1939,9 +1939,9 @@ label { display: block; margin-bottom: 10px; } -This code is a bit repetitive. However, that's not a good reason to combine it into a single Effect! If you did this, you'd have to combine both Effect's dependencies into one list, and then changing the planet would refetch the list of all planets. Effects are not a tool for code reuse. +Этот код немного повторяется. Однако это не лучшая причина для объединения его в один эффект! Если бы вы это сделали, вам пришлось бы объединить обе зависимости эффекта в один список, и тогда изменение планеты привело бы к повторному получению списка всех планет. Эффекты — это не инструмент для повторного использования кода. -Instead, to reduce repetition, you can extract some logic into a custom Hook like `useSelectOptions` below: +Вместо этого, чтобы уменьшить повторение, вы можете извлечь некоторую логику в пользовательский хук, такой как `useSelectOptions` ниже: @@ -2102,8 +2102,8 @@ label { display: block; margin-bottom: 10px; } -Check the `useSelectOptions.js` tab in the sandbox to see how it works. Ideally, most Effects in your application should eventually be replaced by custom Hooks, whether written by you or by the community. Custom Hooks hide the synchronization logic, so the calling component doesn't know about the Effect. As you keep working on your app, you'll develop a palette of Hooks to choose from, and eventually you won't need to write Effects in your components very often. +Проверьте вкладку `useSelectOptions.js` в песочнице, чтобы увидеть, как это работает. В идеале, большинство эффектов в вашем приложении в конечном итоге должны быть заменены пользовательскими хуками, написанными вами или сообществом. Пользовательские хуки скрывают логику синхронизации, поэтому вызывающий компонент не знает об эффекте. По мере продолжения работы над приложением вы будете разрабатывать набор хуков на выбор, и в конечном итоге вам не придется часто писать эффекты в своих компонентах. - + \ No newline at end of file From 5f78ecb4a952f5641ff83ee9cecd75fd39887bf0 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:35:24 +0000 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20translate=20`lifecycle-of-reactive-?= =?UTF-8?q?effects.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/lifecycle-of-reactive-effects.md | 383 +++++++++--------- 1 file changed, 194 insertions(+), 189 deletions(-) diff --git a/src/content/learn/lifecycle-of-reactive-effects.md b/src/content/learn/lifecycle-of-reactive-effects.md index c650b15733..e6853f381c 100644 --- a/src/content/learn/lifecycle-of-reactive-effects.md +++ b/src/content/learn/lifecycle-of-reactive-effects.md @@ -4,34 +4,35 @@ title: 'Жизненный цикл реактивных эффектов' -Эффекты имеют другой жизненный цикл по сравнению с компонентами. Компоненты могут монтироваться, обновляться или размонтироваться. Эффект может делать только две вещи: начать синхронизацию чего-либо и позже прекратить её. Этот цикл может повторяться несколько раз, если ваш эффект зависит от пропсов и состояния, которые меняются со временем. React предоставляет правило линтера для проверки правильности указания зависимостей вашего эффекта. Это гарантирует синхронизацию вашего эффекта с последними пропсами и состоянием. +Эффекты имеют жизненный цикл, отличный от жизненного цикла компонентов. Компоненты могут смонтироваться, обновиться или размонтироваться. Эффект может делать только две вещи: начать что-то синхронизировать, а затем остановить это. Этот цикл может происходить несколько раз, если ваш эффект зависит от пропсов и состояния, которые со временем меняются. React предоставляет правило линтера, чтобы проверить, правильно ли вы указали зависимости вашего эффекта. Это позволяет вашему эффекту синхронизироваться с последними пропсами и состоянием. - Чем жизненный цикл эффекта отличается от жизненного цикла компонента -- Как рассматривать каждый отдельный эффект в изоляции +- Как думать о каждом отдельном эффекте в изоляции - Когда вашему эффекту нужно повторно синхронизироваться и почему - Как определяются зависимости вашего эффекта -- Что означает реактивность значения +- Что значит, когда значение реактивное - Что означает пустой массив зависимостей - Как React проверяет правильность ваших зависимостей с помощью линтера - Что делать, если вы не согласны с линтером -## Жизненный цикл эффекта {/*the-lifecycle-of-an-effect*/} -Каждый компонент React проходит через один и тот же жизненный цикл: +## Жизненный цикл Effect {/*the-lifecycle-of-an-effect*/} + +Каждый React-компонент проходит через один и тот же жизненный цикл: - Компонент _монтируется_, когда он добавляется на экран. - Компонент _обновляется_, когда он получает новые пропсы или состояние, обычно в ответ на взаимодействие. - Компонент _размонтируется_, когда он удаляется с экрана. -**Это хороший способ думать о компонентах, но _не_ об эффектах.** Вместо этого старайтесь думать о каждом эффекте независимо от жизненного цикла вашего компонента. Эффект описывает, как [синхронизировать внешнюю систему](/learn/synchronizing-with-effects) с текущими пропсами и состоянием. По мере изменения вашего кода синхронизация будет происходить чаще или реже. +**Это хороший способ думать о компонентах, но _не_ об Effects.** Вместо этого попробуйте думать о каждом Effect независимо от жизненного цикла вашего компонента. Effect описывает, как [синхронизировать внешнюю систему](/learn/synchronizing-with-effects) с текущими пропсами и состоянием. По мере изменения вашего кода синхронизация должна будет происходить чаще или реже. -Чтобы проиллюстрировать это, рассмотрим эффект, подключающий ваш компонент к чат-серверу: +Чтобы проиллюстрировать это, рассмотрим этот Effect, соединяющий ваш компонент с сервером чата: ```js const serverUrl = 'https://localhost:1234'; @@ -48,7 +49,7 @@ function ChatRoom({ roomId }) { } ``` -Тело вашего эффекта определяет, как **начать синхронизацию:** +Тело вашего Effect указывает, как **начать синхронизацию**: ```js {2-3} // ... @@ -60,7 +61,7 @@ function ChatRoom({ roomId }) { // ... ``` -Функция очистки, возвращаемая вашим эффектом, определяет, как **прекратить синхронизацию:** +Функция очистки, возвращаемая вашим Effect, указывает, как **остановить синхронизацию**: ```js {5} // ... @@ -72,19 +73,19 @@ function ChatRoom({ roomId }) { // ... ``` -Интуитивно вы можете подумать, что React будет **начинать синхронизацию**, когда ваш компонент монтируется, и **прекращать синхронизацию**, когда ваш компонент размонтируется. Однако это еще не все! Иногда может потребоваться **начинать и прекращать синхронизацию несколько раз**, пока компонент остается смонтированным. +Интуитивно вы можете подумать, что React будет **начинать синхронизацию**, когда ваш компонент монтируется, и **останавливать синхронизацию**, когда ваш компонент размонтируется. Однако это еще не конец истории! Иногда может также потребоваться **запускать и останавливать синхронизацию несколько раз**, пока компонент остается смонтированным. -Давайте посмотрим, _почему_ это необходимо, _когда_ это происходит и _как_ вы можете контролировать это поведение. +Давайте посмотрим, _почему_ это необходимо, _когда_ это происходит и _как_ вы можете управлять этим поведением. -Некоторые эффекты вообще не возвращают функцию очистки. [Чаще всего](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) вы захотите ее вернуть — но если нет, React будет вести себя так, как будто вы вернули пустую функцию очистки. +Некоторые Effects вообще не возвращают функцию очистки. [Чаще всего](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development), вам захочется вернуть ее, но если вы этого не сделаете, React будет вести себя так, как если бы вы вернули пустую функцию очистки. ### Почему синхронизация может потребоваться более одного раза {/*why-synchronization-may-need-to-happen-more-than-once*/} -Представьте, что компонент `ChatRoom` получает пропс `roomId`, который пользователь выбирает в выпадающем списке. Допустим, изначально пользователь выбирает комнату `"general"` в качестве `roomId`. Ваше приложение отображает чат-комнату `"general"`: +Представьте, что этот компонент `ChatRoom` получает пропс `roomId`, который пользователь выбирает в выпадающем списке. Допустим, изначально пользователь выбирает комнату `"general"` в качестве `roomId`. Ваше приложение отображает комнату чата `"general"`: ```js {3} const serverUrl = 'https://localhost:1234'; @@ -95,7 +96,7 @@ function ChatRoom({ roomId /* "general" */ }) { } ``` -После отображения пользовательского интерфейса React запустит ваш эффект для **начала синхронизации.** Он подключается к комнате `"general"`: +После отображения пользовательского интерфейса React запустит ваш Effect, чтобы **начать синхронизацию**. Он подключается к комнате `"general"`: ```js {3,4} function ChatRoom({ roomId /* "general" */ }) { @@ -120,20 +121,20 @@ function ChatRoom({ roomId /* "travel" */ }) { } ``` -Подумайте, что должно произойти дальше. Пользователь видит, что `"travel"` — это выбранная комната чата в пользовательском интерфейсе. Однако эффект, который был запущен в прошлый раз, все еще подключен к комнате `"general"`. **Пропс `roomId` изменился, поэтому то, что ваш эффект сделал тогда (подключение к комнате `"general"`), больше не соответствует пользовательскому интерфейсу.** +Подумайте о том, что должно произойти дальше. Пользователь видит, что `"travel"` — это выбранная комната чата в пользовательском интерфейсе. Однако Effect, который работал в прошлый раз, все еще подключен к комнате `"general"`. **Пропс `roomId` изменился, поэтому то, что сделал ваш Effect тогда (подключение к комнате `"general"`), больше не соответствует пользовательскому интерфейсу.** В этот момент вы хотите, чтобы React сделал две вещи: -1. Прекратить синхронизацию со старым `roomId` (отключиться от комнаты `"general"`) +1. Остановить синхронизацию со старым `roomId` (отключиться от комнаты `"general"`) 2. Начать синхронизацию с новым `roomId` (подключиться к комнате `"travel"`) -**К счастью, вы уже научили React делать обе эти вещи!** Тело вашего эффекта определяет, как начать синхронизацию, а ваша функция очистки определяет, как прекратить синхронизацию. Все, что нужно сделать React, — это вызвать их в правильном порядке и с правильными пропсами и состоянием. Давайте посмотрим, как именно это происходит. +**К счастью, вы уже научили React делать и то, и другое!** Тело вашего Effect указывает, как начать синхронизацию, а функция очистки указывает, как остановить синхронизацию. Все, что нужно сделать React, — это вызвать их в правильном порядке и с правильными пропсами и состоянием. Давайте посмотрим, как именно это происходит. -### Как React повторно синхронизирует ваш эффект {/*how-react-re-synchronizes-your-effect*/} +### Как React повторно синхронизирует ваш Effect {/*how-react-re-synchronizes-your-effect*/} -Помните, что ваш компонент `ChatRoom` получил новое значение для своего пропса `roomId`. Раньше это было `"general"`, а теперь `"travel"`. React необходимо повторно синхронизировать ваш эффект, чтобы переподключить вас к другой комнате. +Вспомните, что ваш компонент `ChatRoom` получил новое значение для своего пропса `roomId`. Раньше это было `"general"`, а теперь это `"travel"`. React необходимо повторно синхронизировать ваш Effect, чтобы повторно подключить вас к другой комнате. -Чтобы **прекратить синхронизацию,** React вызовет функцию очистки, которую ваш эффект вернул после подключения к комнате `"general"`. Поскольку `roomId` был `"general"`, функция очистки отключается от комнаты `"general"`: +Чтобы **остановить синхронизацию**, React вызовет функцию очистки, которую ваш Effect вернул после подключения к комнате `"general"`. Поскольку `roomId` был равен `"general"`, функция очистки отключается от комнаты `"general"`: ```js {6} function ChatRoom({ roomId /* "general" */ }) { @@ -146,7 +147,7 @@ function ChatRoom({ roomId /* "general" */ }) { // ... ``` -Затем React запустит эффект, который вы предоставили во время этого рендеринга. На этот раз `roomId` будет `"travel"`, поэтому он **начнет синхронизацию** с чат-комнатой `"travel"` (до тех пор, пока его функция очистки не будет вызвана снова). +Затем React запустит Effect, который вы предоставили во время этого рендера. На этот раз `roomId` равен `"travel"`, поэтому он **начнет синхронизацию** с комнатой чата `"travel"` (до тех пор, пока в конечном итоге не будет вызвана и его функция очистки): ```js {3,4} function ChatRoom({ roomId /* "travel" */ }) { @@ -156,57 +157,57 @@ function ChatRoom({ roomId /* "travel" */ }) { // ... ``` -Благодаря этому вы теперь подключены к той же комнате, которую выбрал пользователь в пользовательском интерфейсе. Катастрофа предотвращена! +Благодаря этому вы теперь подключены к той же комнате, которую пользователь выбрал в пользовательском интерфейсе. Катастрофы удалось избежать! -Каждый раз после повторного рендеринга вашего компонента с измененным `roomId` ваш эффект будет повторно синхронизироваться. Например, скажем, пользователь изменяет `roomId` с `"travel"` на `"music"`. React снова **прекратит синхронизацию** вашего эффекта, вызвав его функцию очистки (отключив вас от комнаты `"travel"`). Затем он снова **начнет синхронизацию**, запустив тело эффекта с новым пропсом `roomId` (подключив вас к комнате `"music"`). +Каждый раз после того, как ваш компонент перерендеривается с другим `roomId`, ваш Effect будет повторно синхронизироваться. Например, допустим, пользователь меняет `roomId` с `"travel"` на `"music"`. React снова **остановит синхронизацию** вашего Effect, вызвав его функцию очистки (отключив вас от комнаты `"travel"`). Затем он **начнет синхронизацию** снова, запустив его тело с новым пропсом `roomId` (подключив вас к комнате `"music"`). -Наконец, когда пользователь переходит на другой экран, `ChatRoom` размонтируется. Теперь нет необходимости оставаться подключенным. React **прекратит синхронизацию** вашего эффекта в последний раз и отключит вас от чат-комнаты `"music"`. +Наконец, когда пользователь переходит на другой экран, `ChatRoom` размонтируется. Теперь вообще нет необходимости оставаться подключенным. React **остановит синхронизацию** вашего Effect в последний раз и отключит вас от комнаты чата `"music"`. -### Мышление с точки зрения эффекта {/*thinking-from-the-effects-perspective*/} +### Размышления с точки зрения Effect {/*thinking-from-the-effects-perspective*/} -Подведем итог всему, что произошло с точки зрения компонента `ChatRoom`: +Давайте подведем итоги всего, что произошло с точки зрения компонента `ChatRoom`: 1. `ChatRoom` смонтировался с `roomId`, установленным в `"general"` 1. `ChatRoom` обновился с `roomId`, установленным в `"travel"` 1. `ChatRoom` обновился с `roomId`, установленным в `"music"` 1. `ChatRoom` размонтировался -Во время каждой из этих точек жизненного цикла вашего компонента ваш эффект делал разные вещи: +В течение каждого из этих моментов жизненного цикла компонента ваш Effect делал разные вещи: -1. Ваш эффект подключился к комнате `"general"` -1. Ваш эффект отключился от комнаты `"general"` и подключился к комнате `"travel"` -1. Ваш эффект отключился от комнаты `"travel"` и подключился к комнате `"music"` -1. Ваш эффект отключился от комнаты `"music"` +1. Ваш Effect подключился к комнате `"general"` +1. Ваш Effect отключился от комнаты `"general"` и подключился к комнате `"travel"` +1. Ваш Effect отключился от комнаты `"travel"` и подключился к комнате `"music"` +1. Ваш Effect отключился от комнаты `"music"` -Теперь давайте подумаем о том, что произошло с точки зрения самого эффекта: +Теперь давайте подумаем о том, что произошло с точки зрения самого Effect: ```js useEffect(() => { - // Ваш эффект подключился к комнате, указанной в roomId... + // Ваш Effect подключился к комнате, указанной с помощью roomId... const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { - // ...пока не отключился + // ...пока он не отключился connection.disconnect(); }; }, [roomId]); ``` -Структура этого кода может вдохновить вас увидеть, что произошло, как последовательность перекрывающихся периодов времени: +Структура этого кода может вдохновить вас увидеть произошедшее как последовательность неперекрывающихся периодов времени: -1. Ваш эффект подключился к комнате `"general"` (пока не отключился) -1. Ваш эффект подключился к комнате `"travel"` (пока не отключился) -1. Ваш эффект подключился к комнате `"music"` (пока не отключился) +1. Ваш Effect подключился к комнате `"general"` (пока не отключился) +1. Ваш Effect подключился к комнате `"travel"` (пока не отключился) +1. Ваш Effect подключился к комнате `"music"` (пока не отключился) -Ранее вы думали с точки зрения компонента. Когда вы смотрели с точки зрения компонента, было заманчиво думать об эффектах как о "колбэках" или "событиях жизненного цикла", которые срабатывают в определенное время, например "после рендеринга" или "перед размонтированием". Такой способ мышления очень быстро усложняется, поэтому его лучше избегать. +Раньше вы думали с точки зрения компонента. Когда вы смотрели с точки зрения компонента, было заманчиво думать об Effects как о «обратных вызовах» или «событиях жизненного цикла», которые срабатывают в определенное время, например «после рендера» или «перед размонтированием». Этот способ мышления очень быстро усложняется, поэтому его лучше избегать. -**Вместо этого всегда фокусируйтесь на одном цикле запуска/остановки за раз. Не должно иметь значения, монтируется, обновляется или размонтируется компонент. Все, что вам нужно сделать, — это описать, как начать синхронизацию и как ее прекратить. Если вы сделаете это хорошо, ваш эффект будет устойчив к запуску и остановке столько раз, сколько потребуется.** +**Вместо этого всегда сосредотачивайтесь на одном цикле запуска/остановки за раз. Не должно иметь значения, монтируется, обновляется или размонтируется компонент. Все, что вам нужно сделать, — это описать, как начать синхронизацию и как ее остановить. Если вы сделаете это хорошо, ваш Effect будет устойчив к запуску и остановке столько раз, сколько потребуется.** -Это может напомнить вам, как вы не задумываетесь, монтируется или обновляется компонент, когда пишете логику рендеринга, создающую JSX. Вы описываете, что должно быть на экране, а React [сам все выясняет.](/learn/reacting-to-input-with-state) +Это может напомнить вам, как вы не думаете о том, монтируется или обновляется компонент, когда вы пишете логику рендеринга, которая создает JSX. Вы описываете, что должно быть на экране, а React [выясняет остальное.](/learn/reacting-to-input-with-state) -### Как React проверяет, что ваш эффект может повторно синхронизироваться {/*how-react-verifies-that-your-effect-can-re-synchronize*/} +### Как React проверяет, что ваш Effect может повторно синхронизироваться {/*how-react-verifies-that-your-effect-can-re-synchronize*/} -Вот живой пример, с которым вы можете поэкспериментировать. Нажмите "Open chat", чтобы смонтировать компонент `ChatRoom`: +Вот живой пример, с которым вы можете поиграть. Нажмите «Открыть чат», чтобы смонтировать компонент `ChatRoom`: @@ -253,7 +254,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 + '...'); @@ -272,49 +273,50 @@ button { margin-left: 10px; } -Обратите внимание, что при первом монтировании компонента вы увидите три лога: +Обратите внимание, что когда компонент монтируется в первый раз, вы видите три журнала: -1. `✅ Connecting to "general" room at https://localhost:1234...` *(только в режиме разработки)* -1. `❌ Disconnected from "general" room at https://localhost:1234.` *(только в режиме разработки)* +1. `✅ Connecting to "general" room at https://localhost:1234...` *(только для разработки)* +1. `❌ Disconnected from "general" room at https://localhost:1234.` *(только для разработки)* 1. `✅ Connecting to "general" room at https://localhost:1234...` -Первые два лога — это только для режима разработки. В режиме разработки React всегда повторно монтирует каждый компонент один раз. +Первые два журнала предназначены только для разработки. При разработке React всегда переустанавливает каждый компонент один раз. -**React проверяет, что ваш эффект может повторно синхронизироваться, заставляя его сделать это немедленно в режиме разработки.** Это может напомнить вам, как вы открываете дверь и закрываете ее еще раз, чтобы проверить, работает ли замок. React запускает и останавливает ваш эффект один раз дополнительно в режиме разработки, чтобы проверить, [хорошо ли вы реализовали его очистку.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) +**React проверяет, что ваш Effect может повторно синхронизироваться, заставляя его делать это немедленно при разработке.** Это может напомнить вам, как вы открываете дверь и закрываете ее лишний раз, чтобы проверить, работает ли дверной замок. React запускает и останавливает ваш Effect один дополнительный раз при разработке, чтобы проверить, [вы хорошо реализовали его очистку.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) -Основная причина, по которой ваш эффект будет повторно синхронизироваться на практике, заключается в изменении некоторых используемых им данных. В песочнице выше измените выбранную комнату чата. Обратите внимание, как при изменении `roomId` ваш эффект повторно синхронизируется. +Основная причина, по которой ваш Effect будет повторно синхронизироваться на практике, заключается в том, что некоторые данные, которые он использует, изменились. В песочнице выше измените выбранную комнату чата. Обратите внимание, как при изменении `roomId` ваш Effect повторно синхронизируется. -Однако существуют и более необычные случаи, когда повторная синхронизация необходима. Например, попробуйте отредактировать `serverUrl` в песочнице выше, пока чат открыт. Обратите внимание, как эффект повторно синхронизируется в ответ на ваши правки кода. В будущем React может добавить новые функции, которые полагаются на повторную синхронизацию. +Однако есть и более необычные случаи, когда повторная синхронизация необходима. Например, попробуйте отредактировать `serverUrl` в песочнице выше, пока чат открыт. Обратите внимание, как Effect повторно синхронизируется в ответ на ваши изменения в коде. В будущем React может добавить больше функций, которые полагаются на повторную синхронизацию. -### Как React узнает, что эффект нужно повторно синхронизировать {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} +### Как React узнает, что ему нужно повторно синхронизировать Effect {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} -Возможно, вы задаетесь вопросом, как React узнал, что ваш эффект нуждается в повторной синхронизации после изменения `roomId`. Это потому, что *вы сказали React*, что его код зависит от `roomId`, включив его в [список зависимостей:](/learn/synchronizing-with-effects#step-2-specify-the-effect-dependencies) +Возможно, вам интересно, как React узнал, что вашему Effect необходимо повторно синхронизироваться после изменения `roomId`. Это потому, что *вы сказали React*, что его код зависит от `roomId`, включив его в [список зависимостей:](/learn/synchronizing-with-effects#step-2-specify-the-effect-dependencies) ```js {1,3,8} function ChatRoom({ roomId }) { // Пропс roomId может меняться со временем useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Этот эффект читает roomId + const connection = createConnection(serverUrl, roomId); // Этот Effect читает roomId connection.connect(); return () => { connection.disconnect(); }; - }, [roomId]); // Поэтому вы говорите React, что этот эффект "зависит от" roomId + }, [roomId]); // Поэтому вы говорите React, что этот Effect «зависит от» roomId // ... ``` Вот как это работает: -1. Вы знали, что `roomId` — это пропс, что означает, что он может меняться со временем. -2. Вы знали, что ваш эффект читает `roomId` (следовательно, его логика зависит от значения, которое может измениться позже). -3. Вот почему вы указали его как зависимость вашего эффекта (чтобы он повторно синхронизировался при изменении `roomId`). +1. Вы знали, что `roomId` — это пропс, а это значит, что он может меняться со временем. +2. Вы знали, что ваш Effect читает `roomId` (поэтому его логика зависит от значения, которое может измениться позже). +3. Вот почему вы указали его в качестве зависимости вашего Effect (чтобы он повторно синхронизировался при изменении `roomId`). + +Каждый раз после того, как ваш компонент перерендеривается, React будет смотреть на массив зависимостей, который вы передали. Если какое-либо из значений в массиве отличается от значения в том же месте, которое вы передали во время предыдущего рендера, React повторно синхронизирует ваш Effect. -Каждый раз после повторного рендеринга вашего компонента React будет смотреть на массив зависимостей, который вы передали. Если какое-либо из значений в массиве отличается от значения в том же месте, которое вы передали во время предыдущего рендеринга, React повторно синхронизирует ваш эффект. +Например, если вы передали `["general"]` во время начального рендера, а позже вы передали `["travel"]` во время следующего рендера, React сравнит `"general"` и `"travel"`. Это разные значения (по сравнению с [`Object.is`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), поэтому React повторно синхронизирует ваш Effect. С другой стороны, если ваш компонент перерендеривается, но `roomId` не изменился, ваш Effect останется подключенным к той же комнате. -Например, если вы передали `["general"]` во время начального рендеринга, а затем позже передали `["travel"]` во время следующего рендеринга, React сравнит `"general"` и `"travel"`. Это разные значения (сравненные с помощью [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), поэтому React повторно синхронизирует ваш эффект. С другой стороны, если ваш компонент повторно рендерится, но `roomId` не изменился, ваш эффект останется подключенным к той же комнате. -### Каждый эффект представляет отдельный процесс синхронизации {/*each-effect-represents-a-separate-synchronization-process*/} +### Каждый эффект представляет собой отдельный процесс синхронизации {/*each-effect-represents-a-separate-synchronization-process*/} -Старайтесь не добавлять в ваш эффект несвязанную логику только потому, что эта логика должна выполняться одновременно с уже написанным вами эффектом. Например, предположим, вы хотите отправить событие аналитики, когда пользователь посещает комнату. У вас уже есть эффект, зависящий от `roomId`, поэтому вы можете почувствовать искушение добавить туда вызов аналитики: +Не добавляйте не связанную логику в ваш Effect только потому, что эта логика должна выполняться одновременно с Effect, который вы уже написали. Например, предположим, вы хотите отправить событие аналитики, когда пользователь посещает комнату. У вас уже есть Effect, который зависит от `roomId`, поэтому у вас может возникнуть соблазн добавить вызов аналитики туда: ```js {3} function ChatRoom({ roomId }) { @@ -330,7 +332,7 @@ function ChatRoom({ roomId }) { } ``` -Но представьте, что позже вы добавите другую зависимость к этому эффекту, которая потребует переустановки соединения. Если этот эффект будет повторно синхронизироваться, он также вызовет `logVisit(roomId)` для той же комнаты, чего вы не предполагали. Логирование посещения — это **отдельный процесс** от подключения. Напишите их как два отдельных эффекта: +Но представьте, что позже вы добавите еще одну зависимость к этому Effect, которой необходимо восстановить соединение. Если этот Effect повторно синхронизируется, он также вызовет `logVisit(roomId)` для той же комнаты, что вы не планировали. Логирование посещения **— это отдельный процесс** от подключения. Напишите их как два отдельных Effect: ```js {2-4} function ChatRoom({ roomId }) { @@ -346,13 +348,14 @@ function ChatRoom({ roomId }) { } ``` -**Каждый эффект в вашем коде должен представлять отдельный и независимый процесс синхронизации.** +**Каждый Effect в вашем коде должен представлять собой отдельный и независимый процесс синхронизации.** -В приведенном выше примере удаление одного эффекта не нарушит логику другого. Это хороший признак того, что они синхронизируют разные вещи, и поэтому их имело смысл разделить. С другой стороны, если вы разделите единый блок логики на отдельные эффекты, код может выглядеть «чище», но его будет [сложнее поддерживать.](/learn/you-might-not-need-an-effect#chains-of-computations) Вот почему вы должны думать, являются ли процессы одинаковыми или отдельными, а не о том, выглядит ли код чище. +В приведенном выше примере удаление одного Effect не сломает логику другого Effect. Это хороший признак того, что они синхронизируют разные вещи, и поэтому имело смысл разделить их. С другой стороны, если вы разделите целостный фрагмент логики на отдельные Effects, код может выглядеть «чище», но его будет [сложнее поддерживать.](/learn/you-might-not-need-an-effect#chains-of-computations) Вот почему вы должны подумать, являются ли процессы одинаковыми или отдельными, а не о том, выглядит ли код чище. -## Эффекты "реагируют" на реактивные значения {/*effects-react-to-reactive-values*/} -Ваш эффект читает две переменные (`serverUrl` и `roomId`), но вы указали только `roomId` в качестве зависимости: +## Эффекты «реагируют» на реактивные значения {/*effects-react-to-reactive-values*/} + +Ваш Effect считывает две переменные (`serverUrl` и `roomId`), но вы указали только `roomId` в качестве зависимости: ```js {5,10} const serverUrl = 'https://localhost:1234'; @@ -369,32 +372,32 @@ function ChatRoom({ roomId }) { } ``` -Почему `serverUrl` не нужно указывать как зависимость? +Почему `serverUrl` не нужно указывать в качестве зависимости? -Это связано с тем, что `serverUrl` никогда не меняется из-за повторного рендеринга. Он всегда одинаков, независимо от того, сколько раз компонент перерисовывается и почему. Поскольку `serverUrl` никогда не меняется, указывать его как зависимость не имеет смысла. В конце концов, зависимости делают что-то только тогда, когда они меняются со временем! +Это потому, что `serverUrl` никогда не меняется из-за повторного рендеринга. Он всегда один и тот же, независимо от того, сколько раз компонент перерендеривается и почему. Поскольку `serverUrl` никогда не меняется, не имело бы смысла указывать его в качестве зависимости. В конце концов, зависимости делают что-то только тогда, когда они меняются со временем! -С другой стороны, `roomId` может быть другим при повторном рендеринге. **Пропсы, состояние и другие значения, объявленные внутри компонента, являются _реактивными_, потому что они вычисляются во время рендеринга и участвуют в потоке данных React.** +С другой стороны, `roomId` может быть другим при повторном рендеринге. **Пропсы, state и другие значения, объявленные внутри компонента, являются _реактивными_, потому что они вычисляются во время рендеринга и участвуют в потоке данных React.** -Если бы `serverUrl` был переменной состояния, он был бы реактивным. Реактивные значения должны быть включены в зависимости: +Если бы `serverUrl` был переменной state, он был бы реактивным. Реактивные значения должны быть включены в зависимости: ```js {2,5,10} function ChatRoom({ roomId }) { // Пропсы меняются со временем - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // Состояние может меняться со временем + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // State может меняться со временем useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Ваш эффект читает пропсы и состояние + const connection = createConnection(serverUrl, roomId); // Ваш Effect считывает пропсы и state connection.connect(); return () => { connection.disconnect(); }; - }, [roomId, serverUrl]); // Поэтому вы сообщаете React, что этот эффект "зависит от" пропсов и состояния + }, [roomId, serverUrl]); // Поэтому вы говорите React, что этот Effect «зависит от» пропсов и state // ... } ``` -Включив `serverUrl` в зависимости, вы гарантируете, что эффект будет повторно синхронизироваться после его изменения. +Включив `serverUrl` в качестве зависимости, вы гарантируете, что Effect повторно синхронизируется после его изменения. -Попробуйте изменить выбранную комнату чата или отредактировать URL сервера в этой песочнице: +Попробуйте изменить выбранную комнату чата или отредактировать URL-адрес сервера в этой песочнице: @@ -468,11 +471,11 @@ button { margin-left: 10px; } -Всякий раз, когда вы изменяете реактивное значение, такое как `roomId` или `serverUrl`, эффект переподключается к серверу чата. +Всякий раз, когда вы меняете реактивное значение, такое как `roomId` или `serverUrl`, Effect повторно подключается к серверу чата. -### Что означает эффект с пустыми зависимостями {/*what-an-effect-with-empty-dependencies-means*/} +### Что означает Effect с пустыми зависимостями {/*what-an-effect-with-empty-dependencies-means*/} -Что произойдет, если вы вынесете `serverUrl` и `roomId` за пределы компонента? +Что произойдет, если вы переместите и `serverUrl`, и `roomId` за пределы компонента? ```js {1,2} const serverUrl = 'https://localhost:1234'; @@ -490,10 +493,9 @@ function ChatRoom() { } ``` -Теперь код вашего эффекта не использует *никаких* реактивных значений, поэтому его зависимости могут быть пустыми (`[]`). - -С точки зрения компонента, пустой массив зависимостей `[]` означает, что эффект подключается к комнате чата только при монтировании компонента и отключается только при размонтировании компонента. (Имейте в виду, что React все равно [повторно синхронизирует его один раз дополнительно](#how-react-verifies-that-your-effect-can-re-synchronize) в режиме разработки, чтобы протестировать вашу логику.) +Теперь код вашего Effect не использует *никаких* реактивных значений, поэтому его зависимости могут быть пустыми (`[]`). +Если смотреть с точки зрения компонента, пустой массив зависимостей `[]` означает, что этот Effect подключается к комнате чата только при монтировании компонента и отключается только при размонтировании компонента. (Имейте в виду, что React все равно [повторно синхронизирует его дополнительное время](#how-react-verifies-that-your-effect-can-re-synchronize) в процессе разработки, чтобы проверить вашу логику.) @@ -548,52 +550,52 @@ button { margin-left: 10px; } -Однако, если вы [думаете с точки зрения эффекта](#thinking-from-the-effects-perspective), вам вообще не нужно думать о монтировании и размонтировании. Важно то, что вы указали, что делает ваш эффект для начала и прекращения синхронизации. Сегодня у него нет реактивных зависимостей. Но если вы когда-нибудь захотите, чтобы пользователь со временем изменял `roomId` или `serverUrl` (и они станут реактивными), код вашего эффекта не изменится. Вам нужно будет только добавить их в зависимости. +Однако, если вы [подумаете с точки зрения Effect,](#thinking-from-the-effects-perspective) вам вообще не нужно думать о монтировании и размонтировании. Важно то, что вы указали, что ваш Effect делает для запуска и остановки синхронизации. Сегодня у него нет реактивных зависимостей. Но если вы когда-нибудь захотите, чтобы пользователь менял `roomId` или `serverUrl` со временем (и они станут реактивными), код вашего Effect не изменится. Вам нужно будет только добавить их в зависимости. -### Все переменные, объявленные в теле компонента, реактивны {/*all-variables-declared-in-the-component-body-are-reactive*/} +### Все переменные, объявленные в теле компонента, являются реактивными {/*all-variables-declared-in-the-component-body-are-reactive*/} -Пропсы и состояние — это не единственные реактивные значения. Значения, которые вы вычисляете из них, также реактивны. Если пропсы или состояние изменятся, ваш компонент перерисуется, и значения, вычисленные из них, также изменятся. Вот почему все переменные из тела компонента, используемые эффектом, должны быть в списке зависимостей эффекта. +Пропсы и state — не единственные реактивные значения. Значения, которые вы вычисляете из них, также являются реактивными. Если пропсы или state меняются, ваш компонент перерендерится, и значения, вычисленные из них, также изменятся. Вот почему все переменные из тела компонента, используемые Effect, должны быть в списке зависимостей Effect. -Допустим, пользователь может выбрать сервер чата в выпадающем списке, но также может настроить сервер по умолчанию в настройках. Предположим, вы уже поместили состояние настроек в [контекст](/learn/scaling-up-with-reducer-and-context), поэтому вы читаете `settings` из этого контекста. Теперь вы вычисляете `serverUrl` на основе выбранного сервера из пропсов и сервера по умолчанию: +Предположим, что пользователь может выбрать сервер чата в раскрывающемся списке, но он также может настроить сервер по умолчанию в настройках. Предположим, вы уже поместили state настроек в [контекст](/learn/scaling-up-with-reducer-and-context), поэтому вы считываете `settings` из этого контекста. Теперь вы вычисляете `serverUrl` на основе выбранного сервера из пропсов и сервера по умолчанию: ```js {3,5,10} -function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен - const settings = useContext(SettingsContext); // settings реактивен - const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl реактивен +function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive + const settings = useContext(SettingsContext); // settings is reactive + const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl is reactive useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Ваш эффект читает roomId и serverUrl + const connection = createConnection(serverUrl, roomId); // Ваш Effect считывает roomId и serverUrl connection.connect(); return () => { connection.disconnect(); }; - }, [roomId, serverUrl]); // Поэтому ему нужно повторно синхронизироваться при изменении любого из них! + }, [roomId, serverUrl]); // Поэтому ему нужно повторно синхронизироваться, когда что-либо из них меняется! // ... } ``` -В этом примере `serverUrl` не является пропсом или переменной состояния. Это обычная переменная, которую вы вычисляете во время рендеринга. Но она вычисляется во время рендеринга, поэтому может измениться из-за повторного рендеринга. Вот почему она реактивна. +В этом примере `serverUrl` не является пропсом или переменной state. Это обычная переменная, которую вы вычисляете во время рендеринга. Но она вычисляется во время рендеринга, поэтому она может измениться из-за повторного рендеринга. Вот почему она реактивная. -**Все значения внутри компонента (включая пропсы, состояние и переменные в теле вашего компонента) реактивны. Любое реактивное значение может измениться при повторном рендеринге, поэтому вам нужно включить реактивные значения в качестве зависимостей эффекта.** +**Все значения внутри компонента (включая пропсы, state и переменные в теле вашего компонента) являются реактивными. Любое реактивное значение может измениться при повторном рендеринге, поэтому вам нужно включить реактивные значения в качестве зависимостей Effect.** -Другими словами, эффекты "реагируют" на все значения из тела компонента. +Другими словами, Effects «реагируют» на все значения из тела компонента. #### Могут ли глобальные или изменяемые значения быть зависимостями? {/*can-global-or-mutable-values-be-dependencies*/} -Изменяемые значения (включая глобальные переменные) не реактивны. +Изменяемые значения (включая глобальные переменные) не являются реактивными. -**Изменяемое значение, такое как [`location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname), не может быть зависимостью.** Оно изменяемо, поэтому может измениться в любой момент совершенно вне потока данных рендеринга React. Его изменение не вызовет повторный рендеринг вашего компонента. Следовательно, даже если вы укажете его в зависимостях, React *не узнает*, чтобы повторно синхронизировать эффект при его изменении. Это также нарушает правила React, потому что чтение изменяемых данных во время рендеринга (когда вы вычисляете зависимости) нарушает [чистоту рендеринга.](/learn/keeping-components-pure) Вместо этого вы должны читать и подписываться на внешний изменяемый объект с помощью [`useSyncExternalStore`.](/learn/you-might-not-need-an-effect#subscribing-to-an-external-store) +**Изменяемое значение, такое как [`location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname), не может быть зависимостью.** Оно изменяемое, поэтому может измениться в любое время полностью за пределами потока данных рендеринга React. Изменение его не вызовет повторный рендеринг вашего компонента. Поэтому, даже если вы указали его в зависимостях, React *не будет знать*, чтобы повторно синхронизировать Effect при его изменении. Это также нарушает правила React, потому что чтение изменяемых данных во время рендеринга (когда вы вычисляете зависимости) нарушает [чистоту рендеринга.](/learn/keeping-components-pure) Вместо этого вы должны считывать и подписываться на внешнее изменяемое значение с помощью [`useSyncExternalStore`.](/learn/you-might-not-need-an-effect#subscribing-to-an-external-store) -**Изменяемое значение, такое как [`ref.current`](/reference/react/useRef#reference) или то, что вы из него читаете, также не может быть зависимостью.** Сам объект ref, возвращаемый `useRef`, может быть зависимостью, но его свойство `current` намеренно изменяемо. Оно позволяет вам [отслеживать что-то, не вызывая повторный рендеринг.](/learn/referencing-values-with-refs) Но поскольку его изменение не вызывает повторный рендеринг, это не реактивное значение, и React не узнает, чтобы повторно запустить ваш эффект при его изменении. +**Изменяемое значение, такое как [`ref.current`](/reference/react/useRef#reference) или вещи, которые вы считываете из него, также не может быть зависимостью.** Объект ref, возвращаемый `useRef`, сам по себе может быть зависимостью, но его свойство `current` намеренно изменяемое. Это позволяет вам [отслеживать что-то, не вызывая повторный рендеринг.](/learn/referencing-values-with-refs) Но поскольку его изменение не вызывает повторный рендеринг, это не реактивное значение, и React не будет знать, чтобы повторно запустить ваш Effect при его изменении. -Как вы узнаете ниже на этой странице, линтер автоматически проверит эти проблемы. +Как вы узнаете ниже на этой странице, линтер будет проверять эти проблемы автоматически. -### React проверяет, что вы указали каждую реактивную переменную в качестве зависимости {/*react-verifies-that-you-specified-every-reactive-value-as-a-dependency*/} +### React проверяет, что вы указали каждое реактивное значение в качестве зависимости {/*react-verifies-that-you-specified-every-reactive-value-as-a-dependency*/} -Если ваш линтер [настроен для React,](/learn/editor-setup#linting) он проверит, что каждое реактивное значение, используемое кодом вашего эффекта, объявлено как его зависимость. Например, это ошибка линтера, потому что и `roomId`, и `serverUrl` реактивны: +Если ваш линтер [настроен для React,](/learn/editor-setup#linting) он проверит, что каждое реактивное значение, используемое кодом вашего Effect, объявлено в качестве его зависимости. Например, это ошибка линтинга, потому что и `roomId`, и `serverUrl` являются реактивными: @@ -601,14 +603,14 @@ function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; -function ChatRoom({ roomId }) { // roomId реактивен - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl реактивен +function ChatRoom({ roomId }) { // roomId is reactive + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); - }, []); // <-- Здесь что-то не так! + }, []); // <-- Что-то здесь не так! return ( <> @@ -667,13 +669,13 @@ button { margin-left: 10px; } -Это может выглядеть как ошибка React, но на самом деле React указывает на ошибку в вашем коде. И `roomId`, и `serverUrl` могут меняться со временем, но вы забыли повторно синхронизировать ваш эффект при их изменении. Вы останетесь подключены к начальным `roomId` и `serverUrl` даже после того, как пользователь выберет другие значения в пользовательском интерфейсе. +Это может выглядеть как ошибка React, но на самом деле React указывает на ошибку в вашем коде. И `roomId`, и `serverUrl` могут меняться со временем, но вы забываете повторно синхронизировать свой Effect при их изменении. Вы останетесь подключенными к исходному `roomId` и `serverUrl` даже после того, как пользователь выберет другие значения в пользовательском интерфейсе. -Чтобы исправить ошибку, следуйте предложению линтера указать `roomId` и `serverUrl` в качестве зависимостей вашего эффекта: +Чтобы исправить ошибку, следуйте предложению линтера и укажите `roomId` и `serverUrl` в качестве зависимостей вашего Effect: ```js {9} -function ChatRoom({ roomId }) { // roomId реактивен - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl реактивен +function ChatRoom({ roomId }) { // roomId is reactive + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); @@ -689,19 +691,20 @@ function ChatRoom({ roomId }) { // roomId реактивен -В некоторых случаях React *знает*, что значение никогда не меняется, даже если оно объявлено внутри компонента. Например, функция [`set` (установки)](/reference/react/useState#setstate), возвращаемая из `useState`, и объект ref, возвращаемый [`useRef`](/reference/react/useRef), являются *стабильными* — гарантируется, что они не изменятся при повторном рендеринге. Стабильные значения не реактивны, поэтому вы можете опустить их из списка. Включение их разрешено: они не изменятся, так что это не имеет значения. +В некоторых случаях React *знает*, что значение никогда не меняется, даже если оно объявлено внутри компонента. Например, функция [`set`](/reference/react/useState#setstate), возвращаемая из `useState`, и объект ref, возвращаемый из [`useRef`](/reference/react/useRef), являются *стабильными* — гарантируется, что они не изменятся при повторном рендеринге. Стабильные значения не являются реактивными, поэтому вы можете опустить их из списка. Их включение разрешено: они не изменятся, поэтому это не имеет значения. -### Что делать, если вы не хотите повторно синхронизировать {/*what-to-do-when-you-dont-want-to-re-synchronize*/} + +### Что делать, когда вы не хотите повторной синхронизации {/*what-to-do-when-you-dont-want-to-re-synchronize*/} В предыдущем примере вы исправили ошибку линтера, перечислив `roomId` и `serverUrl` в качестве зависимостей. -**Однако вы могли бы вместо этого «доказать» линтеру, что эти значения не являются реактивными,** то есть они *не могут* измениться в результате повторного рендеринга. Например, если `serverUrl` и `roomId` не зависят от рендеринга и всегда имеют одинаковые значения, вы можете вынести их за пределы компонента. Теперь они не нуждаются в зависимостях: +**Однако вместо этого вы можете «доказать» линтеру, что эти значения не являются реактивными,** то есть что они *не могут* измениться в результате повторного рендеринга. Например, если `serverUrl` и `roomId` не зависят от рендеринга и всегда имеют одни и те же значения, вы можете переместить их за пределы компонента. Теперь они не должны быть зависимостями: ```js {1,2,11} -const serverUrl = 'https://localhost:1234'; // serverUrl не является реактивным -const roomId = 'general'; // roomId не является реактивным +const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive +const roomId = 'general'; // roomId is not reactive function ChatRoom() { useEffect(() => { @@ -715,13 +718,13 @@ function ChatRoom() { } ``` -Вы также можете вынести их *внутрь эффекта*. Они не вычисляются во время рендеринга, поэтому не являются реактивными: +Вы также можете переместить их *внутри Effect.* Они не вычисляются во время рендеринга, поэтому они не являются реактивными: ```js {3,4,10} function ChatRoom() { useEffect(() => { - const serverUrl = 'https://localhost:1234'; // serverUrl не является реактивным - const roomId = 'general'; // roomId не является реактивным + const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive + const roomId = 'general'; // roomId is not reactive const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { @@ -732,21 +735,21 @@ function ChatRoom() { } ``` -**Эффекты — это реактивные блоки кода.** Они повторно синхронизируются при изменении значений, которые вы читаете внутри них. В отличие от обработчиков событий, которые выполняются только один раз за взаимодействие, эффекты выполняются всякий раз, когда требуется синхронизация. +**Эффекты — это реактивные блоки кода.** Они повторно синхронизируются, когда изменяются значения, которые вы читаете внутри них. В отличие от обработчиков событий, которые запускаются только один раз за взаимодействие, Эффекты запускаются всякий раз, когда необходима синхронизация. -**Вы не можете «выбирать» свои зависимости.** Ваши зависимости должны включать каждое [реактивное значение](#all-variables-declared-in-the-component-body-are-reactive), которое вы читаете в эффекте. Линтер обеспечивает это. Иногда это может привести к проблемам, таким как бесконечные циклы, и к тому, что ваш эффект будет повторно синхронизироваться слишком часто. Не исправляйте эти проблемы, подавляя линтер! Вот что стоит попробовать вместо этого: +**Вы не можете «выбрать» свои зависимости.** Ваши зависимости должны включать все [реактивные значения](#all-variables-declared-in-the-component-body-are-reactive), которые вы читаете в Effect. Линтер обеспечивает это. Иногда это может приводить к проблемам, таким как бесконечные циклы, и к тому, что ваш Effect слишком часто повторно синхронизируется. Не исправляйте эти проблемы, подавляя линтер! Вместо этого попробуйте следующее: -* **Проверьте, представляет ли ваш эффект независимый процесс синхронизации.** Если ваш эффект ничего не синхронизирует, [он может быть ненужным.](/learn/you-might-not-need-an-effect) Если он синхронизирует несколько независимых вещей, [разделите его.](#each-effect-represents-a-separate-synchronization-process) +* **Убедитесь, что ваш Effect представляет собой независимый процесс синхронизации.** Если ваш Effect ничего не синхронизирует, [он может быть ненужным.](/learn/you-might-not-need-an-effect) Если он синхронизирует несколько независимых вещей, [разделите его.](#each-effect-represents-a-separate-synchronization-process) -* **Если вы хотите прочитать последнее значение пропсов или состояния, не «реагируя» на него и не повторно синхронизируя эффект,** вы можете разделить ваш эффект на реактивную часть (которую вы оставите в эффекте) и нереактивную часть (которую вы вынесете во что-то под названием _Событие эффекта_). [Читайте о разделении событий и эффектов.](/learn/separating-events-from-effects) +* **Если вы хотите прочитать последнее значение пропсов или состояния, не «реагируя» на него и не повторно синхронизируя Effect,** вы можете разделить свой Effect на реактивную часть (которую вы сохраните в Effect) и нереактивную часть (которую вы извлечете в то, что называется _Effect Event_). [Прочтите о разделении событий и эффектов.](/learn/separating-events-from-effects) -* **Избегайте использования объектов и функций в качестве зависимостей.** Если вы создаете объекты и функции во время рендеринга, а затем читаете их из эффекта, они будут отличаться при каждом рендеринге. Это приведет к повторной синхронизации эффекта каждый раз. [Узнайте больше об удалении ненужных зависимостей из эффектов.](/learn/removing-effect-dependencies) +* **Избегайте полагаться на объекты и функции в качестве зависимостей.** Если вы создаете объекты и функции во время рендеринга, а затем читаете их из Effect, они будут разными при каждом рендеринге. Это приведет к повторной синхронизации вашего Effect каждый раз. [Прочтите больше об удалении ненужных зависимостей из Effects.](/learn/removing-effect-dependencies) -Линтер — ваш друг, но его возможности ограничены. Линтер знает только, когда зависимости *неправильные*. Он не знает *лучшего* способа решения каждого случая. Если линтер предлагает зависимость, но ее добавление вызывает цикл, это не значит, что линтер следует игнорировать. Вам нужно изменить код внутри (или вне) эффекта так, чтобы это значение не было реактивным и не *требовало* быть зависимостью. +Линтер — ваш друг, но его возможности ограничены. Линтер знает только, когда зависимости *неправильные*. Он не знает *лучшего* способа решить каждый случай. Если линтер предлагает зависимость, но ее добавление вызывает цикл, это не означает, что линтером следует пренебречь. Вам нужно изменить код внутри (или за пределами) Effect, чтобы это значение не было реактивным и не *должно* было быть зависимостью. -Если у вас есть существующая кодовая база, у вас могут быть эффекты, которые подавляют линтер, например: +Если у вас есть существующая кодовая база, у вас могут быть некоторые Effects, которые подавляют линтер следующим образом: ```js {3-4} useEffect(() => { @@ -756,34 +759,34 @@ useEffect(() => { }, []); ``` -На [следующих](/learn/separating-events-from-effects) [страницах](/learn/removing-effect-dependencies) вы узнаете, как исправить этот код, не нарушая правил. Всегда стоит исправлять! +На [следующих](/learn/separating-events-from-effects) [страницах](/learn/removing-effect-dependencies) вы узнаете, как исправить этот код, не нарушая правил. Это всегда стоит исправить! - Компоненты могут монтироваться, обновляться и размонтироваться. -- Каждый эффект имеет отдельный жизненный цикл от окружающего компонента. -- Каждый эффект описывает отдельный процесс синхронизации, который может *начинаться* и *заканчиваться*. -- При написании и чтении эффектов думайте с точки зрения каждого отдельного эффекта (как начать и остановить синхронизацию), а не с точки зрения компонента (как он монтируется, обновляется или размонтируется). +- Каждый Effect имеет отдельный жизненный цикл от окружающего компонента. +- Каждый Effect описывает отдельный процесс синхронизации, который может *запускаться* и *останавливаться*. +- Когда вы пишете и читаете Effects, думайте с точки зрения каждого отдельного Effect (как начать и остановить синхронизацию), а не с точки зрения компонента (как он монтируется, обновляется или размонтируется). - Значения, объявленные внутри тела компонента, являются «реактивными». -- Реактивные значения должны повторно синхронизировать эффект, поскольку они могут меняться со временем. -- Линтер проверяет, что все реактивные значения, используемые внутри эффекта, указаны в качестве зависимостей. -- Все ошибки, отмеченные линтером, являются обоснованными. Всегда есть способ исправить код, чтобы не нарушать правила. +- Реактивные значения должны повторно синхронизировать Effect, потому что они могут меняться со временем. +- Линтер проверяет, что все реактивные значения, используемые внутри Effect, указаны в качестве зависимостей. +- Все ошибки, отмеченные линтером, являются законными. Всегда есть способ исправить код, чтобы не нарушать правила. -#### Исправить повторное подключение при каждом нажатии клавиши {/*fix-reconnecting-on-every-keystroke*/} +#### Исправьте повторное подключение при каждом нажатии клавиши {/*fix-reconnecting-on-every-keystroke*/} -В этом примере компонент `ChatRoom` подключается к чат-комнате при монтировании компонента, отключается при размонтировании и переподключается при выборе другой чат-комнаты. Это поведение корректно, поэтому вам нужно сохранить его работоспособность. +В этом примере компонент `ChatRoom` подключается к чат-комнате при монтировании компонента, отключается при размонтировании и повторно подключается при выборе другой чат-комнаты. Это поведение корректно, поэтому вам нужно сохранить его работоспособность. -Однако есть проблема. Каждый раз, когда вы печатаете в поле ввода сообщения внизу, `ChatRoom` *также* переподключается к чату. (Вы можете заметить это, очистив консоль и напечатав что-нибудь во входном поле.) Исправьте проблему так, чтобы этого не происходило. +Однако есть проблема. Всякий раз, когда вы вводите текст в поле ввода сообщений внизу, `ChatRoom` *также* повторно подключается к чату. (Вы можете заметить это, очистив консоль и набрав текст в поле ввода.) Исправьте проблему, чтобы этого не происходило. -Возможно, вам потребуется добавить массив зависимостей для этого эффекта. Какие зависимости там должны быть? +Возможно, вам потребуется добавить массив зависимостей для этого Effect. Какие зависимости должны быть там? @@ -860,7 +863,7 @@ button { margin-left: 10px; } -У этого эффекта вообще не было массива зависимостей, поэтому он повторно синхронизировался после каждого рендеринга. Сначала добавьте массив зависимостей. Затем убедитесь, что каждое реактивное значение, используемое эффектом, указано в массиве. Например, `roomId` является реактивным (поскольку это пропс), поэтому он должен быть включен в массив. Это гарантирует, что при выборе пользователем другой комнаты чат переподключится. С другой стороны, `serverUrl` определен вне компонента. Поэтому его не нужно включать в массив. +У этого Effect вообще не было массива зависимостей, поэтому он повторно синхронизировался после каждого повторного рендеринга. Сначала добавьте массив зависимостей. Затем убедитесь, что каждое реактивное значение, используемое Effect, указано в массиве. Например, `roomId` является реактивным (потому что это пропс), поэтому его следует включить в массив. Это гарантирует, что при выборе пользователем другой комнаты чат переподключится. С другой стороны, `serverUrl` определяется за пределами компонента. Вот почему его не нужно включать в массив. @@ -937,13 +940,13 @@ button { margin-left: 10px; } #### Включение и выключение синхронизации {/*switch-synchronization-on-and-off*/} -В этом примере эффект подписывается на событие `pointermove` окна, чтобы перемещать розовую точку на экране. Попробуйте навести курсор на область предварительного просмотра (или прикоснуться к экрану, если вы находитесь на мобильном устройстве) и посмотрите, как розовая точка следует за вашим движением. +В этом примере Effect подписывается на событие [`pointermove`](https://developer.mozilla.org/ru/docs/Web/API/Element/pointermove_event) окна, чтобы переместить розовую точку на экране. Попробуйте навести курсор на область предварительного просмотра (или коснуться экрана, если вы используете мобильное устройство) и посмотрите, как розовая точка следует за вашим движением. -Также есть флажок. Установка флажка переключает переменную состояния `canMove`, но эта переменная состояния нигде в коде не используется. Ваша задача — изменить код так, чтобы при `canMove` равном `false` (флажок снят) точка переставала двигаться. После того как вы снова установите флажок (и установите `canMove` в `true`), точка снова начнет следовать за движением. Другими словами, возможность перемещения точки должна синхронизироваться с тем, отмечен ли флажок. +Также есть флажок. Установка флажка переключает переменную состояния `canMove`, но эта переменная состояния нигде не используется в коде. Ваша задача — изменить код так, чтобы, когда `canMove` имеет значение `false` (флажок снят), точка перестала двигаться. После того, как вы снова включите флажок (и установите для `canMove` значение `true`), поле снова должно следовать за движением. Другими словами, будет ли точка двигаться или нет, должно оставаться синхронизированным с тем, установлен ли флажок. -Вы не можете объявлять эффект условно. Однако код внутри эффекта может использовать условия! +Вы не можете объявить Effect условно. Однако код внутри Effect может использовать условия! @@ -1057,7 +1060,7 @@ body { -Альтернативно, вы можете обернуть логику *подписки на событие* в условие `if (canMove) { ... }`: +В качестве альтернативы вы можете обернуть логику *подписки на события* в условие `if (canMove) { ... }`: @@ -1113,19 +1116,20 @@ body { -В обоих этих случаях `canMove` является реактивной переменной, которую вы читаете внутри эффекта. Поэтому ее необходимо указать в списке зависимостей эффекта. Это гарантирует, что эффект повторно синхронизируется после каждого изменения ее значения. +В обоих этих случаях `canMove` является реактивной переменной, которую вы читаете внутри Effect. Вот почему ее необходимо указать в списке зависимостей Effect. Это гарантирует, что Effect повторно синхронизируется после каждого изменения ее значения. -#### Исследуем проблему устаревшего значения {/*investigate-a-stale-value-bug*/} -В этом примере розовая точка должна двигаться при включенном флажке и останавливаться при выключенном. Логика для этого уже реализована: обработчик события `handleMove` проверяет переменную состояния `canMove`. +#### Исследуйте ошибку с устаревшим значением {/*investigate-a-stale-value-bug*/} + +В этом примере розовая точка должна двигаться, когда установлен флажок, и должна перестать двигаться, когда флажок снят. Логика для этого уже реализована: обработчик события `handleMove` проверяет переменную состояния `canMove`. -Однако по какой-то причине переменная состояния `canMove` внутри `handleMove` оказывается «устаревшей»: она всегда равна `true`, даже после снятия флажка. Как это возможно? Найдите ошибку в коде и исправьте её. +Однако по какой-то причине переменная состояния `canMove` внутри `handleMove` выглядит «устаревшей»: она всегда `true`, даже после того, как вы снимете флажок. Как это возможно? Найдите ошибку в коде и исправьте ее. -Если вы видите подавленное правило линтера, удалите подавление! Обычно ошибки скрываются именно там. +Если вы видите, что правило линтера подавлено, удалите подавление! Именно там обычно и находятся ошибки. @@ -1155,9 +1159,9 @@ export default function App() {
-Проблема исходного кода заключалась в подавлении линтера зависимостей. Если убрать подавление, вы увидите, что этот эффект зависит от функции `handleMove`. Это имеет смысл: `handleMove` объявлена внутри тела компонента, что делает её реактивным значением. Каждое реактивное значение должно быть указано как зависимость, иначе оно может устареть со временем! +Проблема с исходным кодом заключалась в подавлении линтера зависимостей. Если вы удалите подавление, вы увидите, что этот эффект зависит от функции `handleMove`. Это имеет смысл: `handleMove` объявляется внутри тела компонента, что делает его реактивным значением. Каждое реактивное значение должно быть указано в качестве зависимости, иначе оно со временем может устареть! -Автор исходного кода «обманул» React, сказав, что эффект не зависит (`[]`) ни от каких реактивных значений. Именно поэтому React не пересинхронизировал эффект после изменения `canMove` (и вместе с ним `handleMove`). Поскольку React не пересинхронизировал эффект, `handleMove`, добавленный как слушатель, является функцией `handleMove`, созданной во время начального рендеринга. Во время начального рендеринга `canMove` был `true`, поэтому `handleMove` из начального рендеринга навсегда увидит это значение. +Автор исходного кода «обманул» React, заявив, что эффект не зависит (`[]`) от каких-либо реактивных значений. Вот почему React не пересинхронизировал эффект после изменения `canMove` (и `handleMove` вместе с ним). Поскольку React не пересинхронизировал эффект, `handleMove`, прикрепленный в качестве слушателя, является функцией `handleMove`, созданной во время начального рендеринга. Во время начального рендеринга `canMove` было равно `true`, поэтому `handleMove` из начального рендеринга навсегда будет видеть это значение. -**Если вы никогда не будете подавлять линтер, вы никогда не столкнётесь с проблемами устаревших значений.** Существует несколько способов решить эту проблему, но всегда начинайте с удаления подавления линтера. Затем измените код, чтобы исправить ошибку линтера. +**Если вы никогда не подавляете линтер, вы никогда не увидите проблем с устаревшими значениями.** Есть несколько способов решить эту ошибку, но всегда следует начинать с удаления подавления линтера. Затем измените код, чтобы исправить ошибку линтинга. -Вы можете изменить зависимости эффекта на `[handleMove]`, но поскольку это будет новая функция для каждого рендеринга, вы можете просто убрать массив зависимостей. Тогда эффект *будет* пересинхронизироваться после каждого повторного рендеринга: +Вы можете изменить зависимости эффекта на `[handleMove]`, но поскольку это будет вновь определенная функция для каждого рендеринга, вы можете просто удалить массив зависимостей вообще. Тогда эффект *будет* пересинхронизироваться после каждого повторного рендеринга: @@ -1220,9 +1224,9 @@ export default function App() {
-Это решение работает, но оно не идеально. Если вы добавите `console.log('Resubscribing')` внутрь эффекта, вы заметите, что он повторно подписывается после каждого повторного рендеринга. Повторная подписка — это быстро, но всё же было бы неплохо избегать этого так часто. +Это решение работает, но оно не идеально. Если вы поместите `console.log('Resubscribing')` внутри эффекта, вы заметите, что он переподписывается после каждого повторного рендеринга. Переподписка происходит быстро, но все равно было бы неплохо избежать этого так часто. -Лучшим решением будет переместить функцию `handleMove` *внутрь* эффекта. Тогда `handleMove` не будет реактивным значением, и ваш эффект не будет зависеть от функции. Вместо этого он будет зависеть от `canMove`, которое ваш код теперь считывает изнутри эффекта. Это соответствует желаемому поведению, поскольку ваш эффект теперь будет синхронизироваться со значением `canMove`: +Лучшим решением было бы переместить функцию `handleMove` *внутрь* эффекта. Тогда `handleMove` не будет реактивным значением, и ваш эффект не будет зависеть от функции. Вместо этого ему нужно будет зависеть от `canMove`, которое ваш код теперь считывает внутри эффекта. Это соответствует желаемому поведению, поскольку ваш эффект теперь будет оставаться синхронизированным со значением `canMove`: @@ -1279,9 +1283,9 @@ export default function App() {
-Попробуйте добавить `console.log('Resubscribing')` внутрь тела эффекта и заметьте, что теперь он повторно подписывается только при переключении флажка (`canMove` изменяется) или при редактировании кода. Это делает его лучше предыдущего подхода, который всегда повторно подписывался. - -Вы узнаете более общий подход к этому типу проблем в разделе [Разделение событий и эффектов.](/learn/separating-events-from-effects) +Попробуйте добавить `console.log('Resubscribing')` внутри тела эффекта и заметьте, что теперь он переподписывается только при переключении флажка (изменение `canMove`) или редактировании кода. Это делает его лучше, чем предыдущий подход, который всегда переподписывался. +Вы узнаете более общий подход к этому типу проблем в [Разделение событий и эффектов.](/learn/separating-events-from-effects) -#### Исправление переключателя соединения {/*fix-a-connection-switch*/} -В этом примере сервис чата в `chat.js` предоставляет два разных API: `createEncryptedConnection` и `createUnencryptedConnection`. Корневой компонент `App` позволяет пользователю выбрать, использовать ли шифрование, а затем передает соответствующий метод API дочернему компоненту `ChatRoom` в виде пропса `createConnection`. +#### Исправьте переключение соединения {/*fix-a-connection-switch*/} + +В этом примере служба чата в `chat.js` предоставляет два разных API: `createEncryptedConnection` и `createUnencryptedConnection`. Корневой компонент `App` позволяет пользователю выбрать, использовать ли шифрование или нет, а затем передает соответствующий метод API дочернему компоненту `ChatRoom` в качестве пропса `createConnection`. -Обратите внимание, что изначально в консоли отображаются сообщения о том, что соединение не зашифровано. Попробуйте включить флажок: ничего не произойдет. Однако, если после этого вы смените выбранную комнату, чат переподключится *и* включит шифрование (как вы увидите из сообщений в консоли). Это ошибка. Исправьте ошибку так, чтобы переключение флажка *также* вызывало переподключение чата. +Обратите внимание, что изначально в консоли отображается сообщение о том, что соединение не зашифровано. Попробуйте переключить флажок: ничего не произойдет. Однако, если после этого вы измените выбранную комнату, чат переподключится *и* включит шифрование (как вы увидите из сообщений в консоли). Это баг. Исправьте баг, чтобы переключение флажка *также* приводило к переподключению чата. -Подавление линтера всегда подозрительно. Может ли это быть ошибкой? +Подавление линтера всегда подозрительно. Может ли это быть багом? @@ -1343,7 +1347,7 @@ export default function App() { return ( <>
-То, что `createConnection` является зависимостью, верно. Однако этот код немного хрупкий, поскольку кто-то может отредактировать компонент `App`, чтобы передать в качестве значения этого пропса инлайн-функцию. В этом случае ее значение будет отличаться при каждом перерисовке компонента `App`, поэтому эффект может слишком часто пересинхронизироваться. Чтобы избежать этого, вы можете передать вместо этого `isEncrypted`: +Правильно, что `createConnection` является зависимостью. Однако этот код немного хрупкий, потому что кто-то может отредактировать компонент `App`, чтобы передать встроенную функцию в качестве значения этого пропса. В этом случае его значение будет разным каждый раз, когда компонент `App` перерендеривается, поэтому эффект может пересинхронизироваться слишком часто. Чтобы избежать этого, вы можете передать `isEncrypted`: @@ -1532,7 +1536,7 @@ export default function App() { return ( <>
-

You are going to: {placeId || '???'} on {planetId || '???'}

+

Вы собираетесь: {placeId || '???'} на {planetId || '???'}

); } @@ -1773,12 +1778,12 @@ label { display: block; margin-bottom: 10px; } -Существует два независимых процесса синхронизации: +Есть два независимых процесса синхронизации: - Первый выпадающий список синхронизирован с удаленным списком планет. - Второй выпадающий список синхронизирован с удаленным списком мест для текущего `planetId`. -Поэтому имеет смысл описывать их как два отдельных эффекта. Вот пример того, как вы можете это сделать: +Вот почему имеет смысл описывать их как два отдельных эффекта. Вот пример того, как вы можете это сделать: @@ -1809,7 +1814,7 @@ export default function Page() { useEffect(() => { if (planetId === '') { - // Пока ничего не выбрано в первом списке + // Nothing is selected in the first box yet return; } @@ -1829,7 +1834,7 @@ export default function Page() { return ( <>
-

You are going to: {placeId || '???'} on {planetId || '???'}

+

Вы собираетесь: {placeId || '???'} на {planetId || '???'}

); } @@ -1939,7 +1944,7 @@ label { display: block; margin-bottom: 10px; }
-Этот код немного повторяется. Однако это не лучшая причина для объединения его в один эффект! Если бы вы это сделали, вам пришлось бы объединить обе зависимости эффекта в один список, и тогда изменение планеты привело бы к повторному получению списка всех планет. Эффекты — это не инструмент для повторного использования кода. +Этот код немного повторяющийся. Однако это не является веской причиной для объединения его в один эффект! Если бы вы это сделали, вам пришлось бы объединить зависимости обоих эффектов в один список, и тогда изменение планеты привело бы к повторной выборке списка всех планет. Эффекты — это не инструмент для повторного использования кода. Вместо этого, чтобы уменьшить повторение, вы можете извлечь некоторую логику в пользовательский хук, такой как `useSelectOptions` ниже: @@ -1965,7 +1970,7 @@ export default function Page() { return ( <>
-

You are going to: {placeId || '...'} on {planetId || '...'}

+

Вы собираетесь: {placeId || '...'} на {planetId || '...'}

); } @@ -2102,7 +2107,7 @@ label { display: block; margin-bottom: 10px; }
-Проверьте вкладку `useSelectOptions.js` в песочнице, чтобы увидеть, как это работает. В идеале, большинство эффектов в вашем приложении в конечном итоге должны быть заменены пользовательскими хуками, написанными вами или сообществом. Пользовательские хуки скрывают логику синхронизации, поэтому вызывающий компонент не знает об эффекте. По мере продолжения работы над приложением вы будете разрабатывать набор хуков на выбор, и в конечном итоге вам не придется часто писать эффекты в своих компонентах. +Проверьте вкладку `useSelectOptions.js` в песочнице, чтобы увидеть, как это работает. В идеале, большинство эффектов в вашем приложении должны в конечном итоге быть заменены пользовательскими хуками, будь то написанными вами или сообществом. Пользовательские хуки скрывают логику синхронизации, поэтому вызывающий компонент не знает об эффекте. По мере продолжения работы над своим приложением вы разработаете палитру хуков на выбор, и в конечном итоге вам не придется очень часто писать эффекты в своих компонентах. From d4233c89ca4c691a86adc456abb956a70f23bdf9 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:13:45 +0000 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20translate=20`lifecycle-of-reactive-?= =?UTF-8?q?effects.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/lifecycle-of-reactive-effects.md | 497 +++++++++++------- 1 file changed, 294 insertions(+), 203 deletions(-) diff --git a/src/content/learn/lifecycle-of-reactive-effects.md b/src/content/learn/lifecycle-of-reactive-effects.md index e6853f381c..4fa324520e 100644 --- a/src/content/learn/lifecycle-of-reactive-effects.md +++ b/src/content/learn/lifecycle-of-reactive-effects.md @@ -1,38 +1,36 @@ --- title: 'Жизненный цикл реактивных эффектов' --- - -Эффекты имеют жизненный цикл, отличный от жизненного цикла компонентов. Компоненты могут смонтироваться, обновиться или размонтироваться. Эффект может делать только две вещи: начать что-то синхронизировать, а затем остановить это. Этот цикл может происходить несколько раз, если ваш эффект зависит от пропсов и состояния, которые со временем меняются. React предоставляет правило линтера, чтобы проверить, правильно ли вы указали зависимости вашего эффекта. Это позволяет вашему эффекту синхронизироваться с последними пропсами и состоянием. +Эффекты имеют другой жизненный цикл по сравнению с компонентами. Компоненты могут монтироваться, обновляться или размонтироваться. Эффект может только выполнять две вещи: начать синхронизацию чего-либо и позже прекратить её. Этот цикл может повторяться несколько раз, если ваш Эффект зависит от пропсов и состояния, которые меняются со временем. React предоставляет правило линтера для проверки правильности указания зависимостей вашего Эффекта. Это гарантирует, что ваш Эффект синхронизирован с последними пропсами и состоянием. -- Чем жизненный цикл эффекта отличается от жизненного цикла компонента -- Как думать о каждом отдельном эффекте в изоляции -- Когда вашему эффекту нужно повторно синхронизироваться и почему -- Как определяются зависимости вашего эффекта -- Что значит, когда значение реактивное +- Чем жизненный цикл Эффекта отличается от жизненного цикла компонента +- Как рассматривать каждый отдельный Эффект в изоляции +- Когда ваш Эффект должен повторно синхронизироваться и почему +- Как определяются зависимости вашего Эффекта +- Что означает реактивность значения - Что означает пустой массив зависимостей - Как React проверяет правильность ваших зависимостей с помощью линтера - Что делать, если вы не согласны с линтером +## Жизненный цикл Эффекта {/*the-lifecycle-of-an-effect*/} -## Жизненный цикл Effect {/*the-lifecycle-of-an-effect*/} - -Каждый React-компонент проходит через один и тот же жизненный цикл: +Каждый компонент React проходит через один и тот же жизненный цикл: - Компонент _монтируется_, когда он добавляется на экран. - Компонент _обновляется_, когда он получает новые пропсы или состояние, обычно в ответ на взаимодействие. - Компонент _размонтируется_, когда он удаляется с экрана. -**Это хороший способ думать о компонентах, но _не_ об Effects.** Вместо этого попробуйте думать о каждом Effect независимо от жизненного цикла вашего компонента. Effect описывает, как [синхронизировать внешнюю систему](/learn/synchronizing-with-effects) с текущими пропсами и состоянием. По мере изменения вашего кода синхронизация должна будет происходить чаще или реже. +**Это хороший способ думать о компонентах, но _не_ об Эффектах.** Вместо этого, старайтесь думать о каждом Эффекте независимо от жизненного цикла вашего компонента. Эффект описывает, как [синхронизировать внешнюю систему](/learn/synchronizing-with-effects) с текущими пропсами и состоянием. По мере изменения вашего кода, синхронизация будет требоваться чаще или реже. -Чтобы проиллюстрировать это, рассмотрим этот Effect, соединяющий ваш компонент с сервером чата: +Чтобы проиллюстрировать это, рассмотрим Эффект, который подключает ваш компонент к серверу чата: ```js const serverUrl = 'https://localhost:1234'; @@ -49,7 +47,7 @@ function ChatRoom({ roomId }) { } ``` -Тело вашего Effect указывает, как **начать синхронизацию**: +Тело вашего Эффекта определяет, как **начать синхронизацию:** ```js {2-3} // ... @@ -61,7 +59,7 @@ function ChatRoom({ roomId }) { // ... ``` -Функция очистки, возвращаемая вашим Effect, указывает, как **остановить синхронизацию**: +Функция очистки, возвращаемая вашим Эффектом, определяет, как **прекратить синхронизацию:** ```js {5} // ... @@ -73,19 +71,19 @@ function ChatRoom({ roomId }) { // ... ``` -Интуитивно вы можете подумать, что React будет **начинать синхронизацию**, когда ваш компонент монтируется, и **останавливать синхронизацию**, когда ваш компонент размонтируется. Однако это еще не конец истории! Иногда может также потребоваться **запускать и останавливать синхронизацию несколько раз**, пока компонент остается смонтированным. +Интуитивно вы можете подумать, что React будет **начинать синхронизацию**, когда компонент монтируется, и **прекращать синхронизацию**, когда компонент размонтируется. Однако, это ещё не всё! Иногда может потребоваться **начать и прекратить синхронизацию несколько раз**, пока компонент остаётся смонтированным. -Давайте посмотрим, _почему_ это необходимо, _когда_ это происходит и _как_ вы можете управлять этим поведением. +Давайте разберёмся, _почему_ это необходимо, _когда_ это происходит и _как_ вы можете контролировать это поведение. -Некоторые Effects вообще не возвращают функцию очистки. [Чаще всего](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development), вам захочется вернуть ее, но если вы этого не сделаете, React будет вести себя так, как если бы вы вернули пустую функцию очистки. +Некоторые Эффекты вообще не возвращают функцию очистки. [Чаще всего](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) вы захотите её вернуть — но если нет, React будет вести себя так, как будто вы вернули пустую функцию очистки. ### Почему синхронизация может потребоваться более одного раза {/*why-synchronization-may-need-to-happen-more-than-once*/} -Представьте, что этот компонент `ChatRoom` получает пропс `roomId`, который пользователь выбирает в выпадающем списке. Допустим, изначально пользователь выбирает комнату `"general"` в качестве `roomId`. Ваше приложение отображает комнату чата `"general"`: +Представьте, что компонент `ChatRoom` получает пропс `roomId`, который пользователь выбирает из выпадающего списка. Допустим, изначально пользователь выбирает комнату `"general"` в качестве `roomId`. Ваше приложение отображает чат комнаты `"general"`: ```js {3} const serverUrl = 'https://localhost:1234'; @@ -96,7 +94,7 @@ function ChatRoom({ roomId /* "general" */ }) { } ``` -После отображения пользовательского интерфейса React запустит ваш Effect, чтобы **начать синхронизацию**. Он подключается к комнате `"general"`: +После отображения UI, React запустит ваш Эффект для **начала синхронизации.** Он подключается к комнате `"general"`: ```js {3,4} function ChatRoom({ roomId /* "general" */ }) { @@ -110,9 +108,9 @@ function ChatRoom({ roomId /* "general" */ }) { // ... ``` -Пока все хорошо. +Пока всё хорошо. -Позже пользователь выбирает другую комнату в выпадающем списке (например, `"travel"`). Сначала React обновит пользовательский интерфейс: +Позже пользователь выбирает другую комнату в выпадающем списке (например, `"travel"`). Сначала React обновит UI: ```js {1} function ChatRoom({ roomId /* "travel" */ }) { @@ -121,20 +119,20 @@ function ChatRoom({ roomId /* "travel" */ }) { } ``` -Подумайте о том, что должно произойти дальше. Пользователь видит, что `"travel"` — это выбранная комната чата в пользовательском интерфейсе. Однако Effect, который работал в прошлый раз, все еще подключен к комнате `"general"`. **Пропс `roomId` изменился, поэтому то, что сделал ваш Effect тогда (подключение к комнате `"general"`), больше не соответствует пользовательскому интерфейсу.** +Подумайте, что должно произойти дальше. Пользователь видит, что `"travel"` — это выбранная комната чата в UI. Однако, Эффект, который был запущен в прошлый раз, всё ещё подключён к комнате `"general"`. **Пропс `roomId` изменился, поэтому то, что ваш Эффект сделал тогда (подключение к комнате `"general"`), больше не соответствует UI.** В этот момент вы хотите, чтобы React сделал две вещи: -1. Остановить синхронизацию со старым `roomId` (отключиться от комнаты `"general"`) +1. Прекратить синхронизацию с старым `roomId` (отключиться от комнаты `"general"`) 2. Начать синхронизацию с новым `roomId` (подключиться к комнате `"travel"`) -**К счастью, вы уже научили React делать и то, и другое!** Тело вашего Effect указывает, как начать синхронизацию, а функция очистки указывает, как остановить синхронизацию. Все, что нужно сделать React, — это вызвать их в правильном порядке и с правильными пропсами и состоянием. Давайте посмотрим, как именно это происходит. +**К счастью, вы уже научили React делать обе эти вещи!** Тело вашего Эффекта определяет, как начать синхронизацию, а ваша функция очистки определяет, как её прекратить. Всё, что нужно сделать React — это вызвать их в правильном порядке и с правильными пропсами и состоянием. Давайте посмотрим, как именно это происходит. -### Как React повторно синхронизирует ваш Effect {/*how-react-re-synchronizes-your-effect*/} +### Как React повторно синхронизирует ваш Эффект {/*how-react-re-synchronizes-your-effect*/} -Вспомните, что ваш компонент `ChatRoom` получил новое значение для своего пропса `roomId`. Раньше это было `"general"`, а теперь это `"travel"`. React необходимо повторно синхронизировать ваш Effect, чтобы повторно подключить вас к другой комнате. +Вспомните, что ваш компонент `ChatRoom` получил новое значение для своего пропса `roomId`. Раньше это было `"general"`, а теперь `"travel"`. React должен повторно синхронизировать ваш Эффект, чтобы переподключить вас к другой комнате. -Чтобы **остановить синхронизацию**, React вызовет функцию очистки, которую ваш Effect вернул после подключения к комнате `"general"`. Поскольку `roomId` был равен `"general"`, функция очистки отключается от комнаты `"general"`: +Чтобы **прекратить синхронизацию,** React вызовет функцию очистки, которую ваш Эффект вернул после подключения к комнате `"general"`. Поскольку `roomId` был `"general"`, функция очистки отключается от комнаты `"general"`: ```js {6} function ChatRoom({ roomId /* "general" */ }) { @@ -147,7 +145,7 @@ function ChatRoom({ roomId /* "general" */ }) { // ... ``` -Затем React запустит Effect, который вы предоставили во время этого рендера. На этот раз `roomId` равен `"travel"`, поэтому он **начнет синхронизацию** с комнатой чата `"travel"` (до тех пор, пока в конечном итоге не будет вызвана и его функция очистки): +Затем React запустит Эффект, который вы предоставили во время этого рендера. На этот раз `roomId` будет `"travel"`, поэтому он **начнёт синхронизацию** с комнатой `"travel"` (до тех пор, пока его функция очистки не будет вызвана). ```js {3,4} function ChatRoom({ roomId /* "travel" */ }) { @@ -157,57 +155,57 @@ function ChatRoom({ roomId /* "travel" */ }) { // ... ``` -Благодаря этому вы теперь подключены к той же комнате, которую пользователь выбрал в пользовательском интерфейсе. Катастрофы удалось избежать! +Благодаря этому вы теперь подключены к той же комнате, которую выбрал пользователь в UI. Катастрофа предотвращена! -Каждый раз после того, как ваш компонент перерендеривается с другим `roomId`, ваш Effect будет повторно синхронизироваться. Например, допустим, пользователь меняет `roomId` с `"travel"` на `"music"`. React снова **остановит синхронизацию** вашего Effect, вызвав его функцию очистки (отключив вас от комнаты `"travel"`). Затем он **начнет синхронизацию** снова, запустив его тело с новым пропсом `roomId` (подключив вас к комнате `"music"`). +Каждый раз после повторного рендера вашего компонента с изменённым `roomId`, ваш Эффект будет повторно синхронизироваться. Например, скажем, пользователь меняет `roomId` с `"travel"` на `"music"`. React снова **прекратит синхронизацию** вашего Эффекта, вызвав его функцию очистки (отключив вас от комнаты `"travel"`). Затем он снова **начнёт синхронизацию**, запустив тело Эффекта с новым пропсом `roomId` (подключив вас к комнате `"music"`). -Наконец, когда пользователь переходит на другой экран, `ChatRoom` размонтируется. Теперь вообще нет необходимости оставаться подключенным. React **остановит синхронизацию** вашего Effect в последний раз и отключит вас от комнаты чата `"music"`. +Наконец, когда пользователь переходит на другой экран, `ChatRoom` размонтируется. Теперь нет необходимости оставаться подключённым. React **прекратит синхронизацию** вашего Эффекта в последний раз и отключит вас от комнаты `"music"`. -### Размышления с точки зрения Effect {/*thinking-from-the-effects-perspective*/} +### Мышление с точки зрения Эффекта {/*thinking-from-the-effects-perspective*/} -Давайте подведем итоги всего, что произошло с точки зрения компонента `ChatRoom`: +Подведём итог всему, что произошло с точки зрения компонента `ChatRoom`: 1. `ChatRoom` смонтировался с `roomId`, установленным в `"general"` 1. `ChatRoom` обновился с `roomId`, установленным в `"travel"` 1. `ChatRoom` обновился с `roomId`, установленным в `"music"` 1. `ChatRoom` размонтировался -В течение каждого из этих моментов жизненного цикла компонента ваш Effect делал разные вещи: +В каждый из этих моментов жизненного цикла компонента ваш Эффект делал разные вещи: -1. Ваш Effect подключился к комнате `"general"` -1. Ваш Effect отключился от комнаты `"general"` и подключился к комнате `"travel"` -1. Ваш Effect отключился от комнаты `"travel"` и подключился к комнате `"music"` -1. Ваш Effect отключился от комнаты `"music"` +1. Ваш Эффект подключился к комнате `"general"` +1. Ваш Эффект отключился от комнаты `"general"` и подключился к комнате `"travel"` +1. Ваш Эффект отключился от комнаты `"travel"` и подключился к комнате `"music"` +1. Ваш Эффект отключился от комнаты `"music"` -Теперь давайте подумаем о том, что произошло с точки зрения самого Effect: +Теперь давайте подумаем о том, что произошло с точки зрения самого Эффекта: ```js useEffect(() => { - // Ваш Effect подключился к комнате, указанной с помощью roomId... + // Ваш Эффект подключился к комнате, указанной в roomId... const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { - // ...пока он не отключился + // ...до тех пор, пока не отключился connection.disconnect(); }; }, [roomId]); ``` -Структура этого кода может вдохновить вас увидеть произошедшее как последовательность неперекрывающихся периодов времени: +Структура этого кода может вдохновить вас увидеть, что произошло, как последовательность непересекающихся временных периодов: -1. Ваш Effect подключился к комнате `"general"` (пока не отключился) -1. Ваш Effect подключился к комнате `"travel"` (пока не отключился) -1. Ваш Effect подключился к комнате `"music"` (пока не отключился) +1. Ваш Эффект подключился к комнате `"general"` (до отключения) +1. Ваш Эффект подключился к комнате `"travel"` (до отключения) +1. Ваш Эффект подключился к комнате `"music"` (до отключения) -Раньше вы думали с точки зрения компонента. Когда вы смотрели с точки зрения компонента, было заманчиво думать об Effects как о «обратных вызовах» или «событиях жизненного цикла», которые срабатывают в определенное время, например «после рендера» или «перед размонтированием». Этот способ мышления очень быстро усложняется, поэтому его лучше избегать. +Раньше вы думали с точки зрения компонента. Когда вы смотрели с точки зрения компонента, было заманчиво думать об Эффектах как о "колбэках" или "событиях жизненного цикла", которые срабатывают в определённое время, например, "после рендера" или "перед размонтированием". Такой способ мышления очень быстро усложняется, поэтому его лучше избегать. -**Вместо этого всегда сосредотачивайтесь на одном цикле запуска/остановки за раз. Не должно иметь значения, монтируется, обновляется или размонтируется компонент. Все, что вам нужно сделать, — это описать, как начать синхронизацию и как ее остановить. Если вы сделаете это хорошо, ваш Effect будет устойчив к запуску и остановке столько раз, сколько потребуется.** +**Вместо этого, всегда фокусируйтесь на одном цикле запуска/остановки за раз. Не должно иметь значения, монтируется ли компонент, обновляется или размонтируется. Всё, что вам нужно сделать, это описать, как начать синхронизацию и как её прекратить. Если вы сделаете это хорошо, ваш Эффект будет устойчив к запуску и остановке столько раз, сколько потребуется.** -Это может напомнить вам, как вы не думаете о том, монтируется или обновляется компонент, когда вы пишете логику рендеринга, которая создает JSX. Вы описываете, что должно быть на экране, а React [выясняет остальное.](/learn/reacting-to-input-with-state) +Это может напомнить вам, как вы не задумываетесь, монтируется или обновляется компонент, когда пишете логику рендеринга, создающую JSX. Вы описываете, что должно быть на экране, а React [сам всё выясняет.](/learn/reacting-to-input-with-state) -### Как React проверяет, что ваш Effect может повторно синхронизироваться {/*how-react-verifies-that-your-effect-can-re-synchronize*/} +### Как React проверяет, что ваш Эффект может повторно синхронизироваться {/*how-react-verifies-that-your-effect-can-re-synchronize*/} -Вот живой пример, с которым вы можете поиграть. Нажмите «Открыть чат», чтобы смонтировать компонент `ChatRoom`: +Вот живой пример, с которым вы можете поэкспериментировать. Нажмите "Open chat", чтобы смонтировать компонент `ChatRoom`: @@ -254,7 +252,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 + '...'); @@ -273,50 +271,49 @@ button { margin-left: 10px; } -Обратите внимание, что когда компонент монтируется в первый раз, вы видите три журнала: +Обратите внимание, что при первом монтировании компонента вы увидите три лога: -1. `✅ Connecting to "general" room at https://localhost:1234...` *(только для разработки)* -1. `❌ Disconnected from "general" room at https://localhost:1234.` *(только для разработки)* +1. `✅ Connecting to "general" room at https://localhost:1234...` *(только в режиме разработки)* +1. `❌ Disconnected from "general" room at https://localhost:1234.` *(только в режиме разработки)* 1. `✅ Connecting to "general" room at https://localhost:1234...` -Первые два журнала предназначены только для разработки. При разработке React всегда переустанавливает каждый компонент один раз. +Первые два лога — это только для режима разработки. В режиме разработки React всегда повторно монтирует каждый компонент один раз. -**React проверяет, что ваш Effect может повторно синхронизироваться, заставляя его делать это немедленно при разработке.** Это может напомнить вам, как вы открываете дверь и закрываете ее лишний раз, чтобы проверить, работает ли дверной замок. React запускает и останавливает ваш Effect один дополнительный раз при разработке, чтобы проверить, [вы хорошо реализовали его очистку.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) +**React проверяет, что ваш Эффект может повторно синхронизироваться, заставляя его сделать это немедленно в режиме разработки.** Это может напомнить вам, как вы открываете и закрываете дверь лишний раз, чтобы проверить, работает ли замок. React запускает и останавливает ваш Эффект один раз дополнительно в режиме разработки, чтобы проверить, [хорошо ли вы реализовали его очистку.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) -Основная причина, по которой ваш Effect будет повторно синхронизироваться на практике, заключается в том, что некоторые данные, которые он использует, изменились. В песочнице выше измените выбранную комнату чата. Обратите внимание, как при изменении `roomId` ваш Effect повторно синхронизируется. +Основная причина, по которой ваш Эффект будет повторно синхронизироваться на практике, заключается в изменении каких-либо данных, которые он использует. В песочнице выше измените выбранную комнату чата. Обратите внимание, как при изменении `roomId` ваш Эффект повторно синхронизируется. -Однако есть и более необычные случаи, когда повторная синхронизация необходима. Например, попробуйте отредактировать `serverUrl` в песочнице выше, пока чат открыт. Обратите внимание, как Effect повторно синхронизируется в ответ на ваши изменения в коде. В будущем React может добавить больше функций, которые полагаются на повторную синхронизацию. +Однако существуют и более редкие случаи, когда повторная синхронизация необходима. Например, попробуйте отредактировать `serverUrl` в песочнице выше, пока чат открыт. Обратите внимание, как Эффект повторно синхронизируется в ответ на ваши правки кода. В будущем React может добавить новые функции, которые полагаются на повторную синхронизацию. -### Как React узнает, что ему нужно повторно синхронизировать Effect {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} +### Как React узнаёт, что нужно повторно синхронизировать Эффект {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} -Возможно, вам интересно, как React узнал, что вашему Effect необходимо повторно синхронизироваться после изменения `roomId`. Это потому, что *вы сказали React*, что его код зависит от `roomId`, включив его в [список зависимостей:](/learn/synchronizing-with-effects#step-2-specify-the-effect-dependencies) +Вы можете задаться вопросом, как React узнал, что ваш Эффект нуждается в повторной синхронизации после изменения `roomId`. Это потому, что *вы сказали React*, что его код зависит от `roomId`, включив его в [список зависимостей:](/learn/synchronizing-with-effects#step-2-specify-the-effect-dependencies) ```js {1,3,8} function ChatRoom({ roomId }) { // Пропс roomId может меняться со временем useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Этот Effect читает roomId + const connection = createConnection(serverUrl, roomId); // Этот Эффект читает roomId connection.connect(); return () => { connection.disconnect(); }; - }, [roomId]); // Поэтому вы говорите React, что этот Effect «зависит от» roomId + }, [roomId]); // Поэтому вы говорите React, что этот Эффект "зависит от" roomId // ... ``` Вот как это работает: -1. Вы знали, что `roomId` — это пропс, а это значит, что он может меняться со временем. -2. Вы знали, что ваш Effect читает `roomId` (поэтому его логика зависит от значения, которое может измениться позже). -3. Вот почему вы указали его в качестве зависимости вашего Effect (чтобы он повторно синхронизировался при изменении `roomId`). - -Каждый раз после того, как ваш компонент перерендеривается, React будет смотреть на массив зависимостей, который вы передали. Если какое-либо из значений в массиве отличается от значения в том же месте, которое вы передали во время предыдущего рендера, React повторно синхронизирует ваш Effect. +1. Вы знали, что `roomId` — это пропс, что означает, что он может меняться со временем. +2. Вы знали, что ваш Эффект читает `roomId` (следовательно, его логика зависит от значения, которое может измениться позже). +3. Вот почему вы указали его как зависимость вашего Эффекта (чтобы он повторно синхронизировался при изменении `roomId`). -Например, если вы передали `["general"]` во время начального рендера, а позже вы передали `["travel"]` во время следующего рендера, React сравнит `"general"` и `"travel"`. Это разные значения (по сравнению с [`Object.is`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), поэтому React повторно синхронизирует ваш Effect. С другой стороны, если ваш компонент перерендеривается, но `roomId` не изменился, ваш Effect останется подключенным к той же комнате. +Каждый раз после повторного рендера вашего компонента React будет смотреть на массив зависимостей, который вы передали. Если какое-либо из значений в массиве отличается от значения в том же месте, которое вы передали во время предыдущего рендера, React повторно синхронизирует ваш Эффект. +Например, если вы передали `["general"]` во время начального рендера, а позже передали `["travel"]` во время следующего рендера, React сравнит `"general"` и `"travel"`. Это разные значения (сравнение происходит с помощью [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), поэтому React повторно синхронизирует ваш Эффект. С другой стороны, если ваш компонент повторно рендерится, но `roomId` не изменился, ваш Эффект останется подключённым к той же комнате. -### Каждый эффект представляет собой отдельный процесс синхронизации {/*each-effect-represents-a-separate-synchronization-process*/} +### Каждый Эффект представляет отдельный процесс синхронизации {/*each-effect-represents-a-separate-synchronization-process*/} -Не добавляйте не связанную логику в ваш Effect только потому, что эта логика должна выполняться одновременно с Effect, который вы уже написали. Например, предположим, вы хотите отправить событие аналитики, когда пользователь посещает комнату. У вас уже есть Effect, который зависит от `roomId`, поэтому у вас может возникнуть соблазн добавить вызов аналитики туда: +Воздержитесь от добавления несвязанной логики в ваш Эффект только потому, что эта логика должна выполняться одновременно с уже написанным вами Эффектом. Например, скажем, вы хотите отправлять аналитическое событие при посещении пользователем комнаты. У вас уже есть Эффект, зависящий от `roomId`, поэтому вы можете почувствовать искушение добавить туда вызов аналитики: ```js {3} function ChatRoom({ roomId }) { @@ -332,7 +329,7 @@ function ChatRoom({ roomId }) { } ``` -Но представьте, что позже вы добавите еще одну зависимость к этому Effect, которой необходимо восстановить соединение. Если этот Effect повторно синхронизируется, он также вызовет `logVisit(roomId)` для той же комнаты, что вы не планировали. Логирование посещения **— это отдельный процесс** от подключения. Напишите их как два отдельных Effect: +Но представьте, что позже вы добавите другую зависимость к этому Эффекту, которая потребует переустановки соединения. Если этот Эффект повторно синхронизируется, он также вызовет `logVisit(roomId)` для той же комнаты, чего вы не предполагали. Логирование посещения — это **отдельный процесс** от подключения. Напишите их как два отдельных Эффекта: ```js {2-4} function ChatRoom({ roomId }) { @@ -348,14 +345,13 @@ function ChatRoom({ roomId }) { } ``` -**Каждый Effect в вашем коде должен представлять собой отдельный и независимый процесс синхронизации.** +**Каждый Эффект в вашем коде должен представлять отдельный и независимый процесс синхронизации.** -В приведенном выше примере удаление одного Effect не сломает логику другого Effect. Это хороший признак того, что они синхронизируют разные вещи, и поэтому имело смысл разделить их. С другой стороны, если вы разделите целостный фрагмент логики на отдельные Effects, код может выглядеть «чище», но его будет [сложнее поддерживать.](/learn/you-might-not-need-an-effect#chains-of-computations) Вот почему вы должны подумать, являются ли процессы одинаковыми или отдельными, а не о том, выглядит ли код чище. +В приведённом выше примере удаление одного Эффекта не нарушит логику другого. Это хороший признак того, что они синхронизируют разные вещи, и поэтому имело смысл их разделить. С другой стороны, если вы разделите связный фрагмент логики на отдельные Эффекты, код может выглядеть "чище", но будет [сложнее в поддержке.](/learn/you-might-not-need-an-effect#chains-of-computations) Вот почему вы должны думать, являются ли процессы одинаковыми или разными, а не о том, выглядит ли код чище. +## Эффекты "реагируют" на реактивные значения {/*effects-react-to-reactive-values*/} -## Эффекты «реагируют» на реактивные значения {/*effects-react-to-reactive-values*/} - -Ваш Effect считывает две переменные (`serverUrl` и `roomId`), но вы указали только `roomId` в качестве зависимости: +Ваш эффект считывает две переменные (`serverUrl` и `roomId`), но вы указали `roomId` в качестве зависимости: ```js {5,10} const serverUrl = 'https://localhost:1234'; @@ -372,32 +368,32 @@ function ChatRoom({ roomId }) { } ``` -Почему `serverUrl` не нужно указывать в качестве зависимости? +Почему `serverUrl` не нужно указывать в зависимостях? -Это потому, что `serverUrl` никогда не меняется из-за повторного рендеринга. Он всегда один и тот же, независимо от того, сколько раз компонент перерендеривается и почему. Поскольку `serverUrl` никогда не меняется, не имело бы смысла указывать его в качестве зависимости. В конце концов, зависимости делают что-то только тогда, когда они меняются со временем! +Это связано с тем, что `serverUrl` никогда не меняется из-за повторного рендеринга. Он всегда одинаков, независимо от того, сколько раз компонент повторно рендерится и почему. Поскольку `serverUrl` никогда не меняется, указывать его в качестве зависимости не имеет смысла. В конце концов, зависимости делают что-то только тогда, когда они меняются со временем! -С другой стороны, `roomId` может быть другим при повторном рендеринге. **Пропсы, state и другие значения, объявленные внутри компонента, являются _реактивными_, потому что они вычисляются во время рендеринга и участвуют в потоке данных React.** +С другой стороны, `roomId` может быть разным при повторном рендеринге. **Пропсы, состояние и другие значения, объявленные внутри компонента, являются _реактивными_, потому что они вычисляются во время рендеринга и участвуют в потоке данных React.** -Если бы `serverUrl` был переменной state, он был бы реактивным. Реактивные значения должны быть включены в зависимости: +Если бы `serverUrl` был переменной состояния, он был бы реактивным. Реактивные значения должны быть включены в зависимости: ```js {2,5,10} -function ChatRoom({ roomId }) { // Пропсы меняются со временем - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // State может меняться со временем +function ChatRoom({ roomId }) { // roomId может меняться со временем + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl может меняться со временем useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Ваш Effect считывает пропсы и state + const connection = createConnection(serverUrl, roomId); // Ваш эффект считывает пропсы и состояние connection.connect(); return () => { connection.disconnect(); }; - }, [roomId, serverUrl]); // Поэтому вы говорите React, что этот Effect «зависит от» пропсов и state + }, [roomId, serverUrl]); // Поэтому вы сообщаете React, что этот эффект "зависит" от пропсов и состояния // ... } ``` -Включив `serverUrl` в качестве зависимости, вы гарантируете, что Effect повторно синхронизируется после его изменения. +Включив `serverUrl` в зависимости, вы гарантируете, что эффект будет повторно синхронизироваться после его изменения. -Попробуйте изменить выбранную комнату чата или отредактировать URL-адрес сервера в этой песочнице: +Попробуйте изменить выбранную комнату чата или отредактировать URL сервера в этой песочнице: @@ -471,11 +467,11 @@ button { margin-left: 10px; } -Всякий раз, когда вы меняете реактивное значение, такое как `roomId` или `serverUrl`, Effect повторно подключается к серверу чата. +Всякий раз, когда вы изменяете реактивное значение, такое как `roomId` или `serverUrl`, эффект переподключается к серверу чата. -### Что означает Effect с пустыми зависимостями {/*what-an-effect-with-empty-dependencies-means*/} +### Что означает эффект с пустыми зависимостями {/*what-an-effect-with-empty-dependencies-means*/} -Что произойдет, если вы переместите и `serverUrl`, и `roomId` за пределы компонента? +Что произойдет, если вы вынесете `serverUrl` и `roomId` за пределы компонента? ```js {1,2} const serverUrl = 'https://localhost:1234'; @@ -493,9 +489,10 @@ function ChatRoom() { } ``` -Теперь код вашего Effect не использует *никаких* реактивных значений, поэтому его зависимости могут быть пустыми (`[]`). +Теперь код вашего эффекта не использует *никаких* реактивных значений, поэтому его зависимости могут быть пустыми (`[]`). + +С точки зрения компонента, пустой массив зависимостей `[]` означает, что эффект подключается к комнате чата только при монтировании компонента и отключается только при размонтировании компонента. (Имейте в виду, что React все равно [повторно синхронизирует его один раз дополнительно](#how-react-verifies-that-your-effect-can-re-synchronize) в режиме разработки, чтобы протестировать вашу логику.) -Если смотреть с точки зрения компонента, пустой массив зависимостей `[]` означает, что этот Effect подключается к комнате чата только при монтировании компонента и отключается только при размонтировании компонента. (Имейте в виду, что React все равно [повторно синхронизирует его дополнительное время](#how-react-verifies-that-your-effect-can-re-synchronize) в процессе разработки, чтобы проверить вашу логику.) @@ -550,34 +547,34 @@ button { margin-left: 10px; } -Однако, если вы [подумаете с точки зрения Effect,](#thinking-from-the-effects-perspective) вам вообще не нужно думать о монтировании и размонтировании. Важно то, что вы указали, что ваш Effect делает для запуска и остановки синхронизации. Сегодня у него нет реактивных зависимостей. Но если вы когда-нибудь захотите, чтобы пользователь менял `roomId` или `serverUrl` со временем (и они станут реактивными), код вашего Effect не изменится. Вам нужно будет только добавить их в зависимости. +Однако, если вы [думаете с точки зрения эффекта](#thinking-from-the-effects-perspective), вам вообще не нужно думать о монтировании и размонтировании. Важно то, что вы указали, как ваш эффект начинает и прекращает синхронизацию. Сегодня у него нет реактивных зависимостей. Но если вы когда-нибудь захотите, чтобы пользователь со временем изменял `roomId` или `serverUrl` (и они станут реактивными), код вашего эффекта не изменится. Вам нужно будет только добавить их в зависимости. -### Все переменные, объявленные в теле компонента, являются реактивными {/*all-variables-declared-in-the-component-body-are-reactive*/} +### Все переменные, объявленные в теле компонента, реактивны {/*all-variables-declared-in-the-component-body-are-reactive*/} -Пропсы и state — не единственные реактивные значения. Значения, которые вы вычисляете из них, также являются реактивными. Если пропсы или state меняются, ваш компонент перерендерится, и значения, вычисленные из них, также изменятся. Вот почему все переменные из тела компонента, используемые Effect, должны быть в списке зависимостей Effect. +Пропсы и состояние — это не единственные реактивные значения. Значения, которые вы вычисляете из них, также реактивны. Если пропсы или состояние изменяются, ваш компонент будет повторно рендериться, и значения, вычисленные из них, также изменятся. Вот почему все переменные из тела компонента, используемые эффектом, должны быть в списке зависимостей эффекта. -Предположим, что пользователь может выбрать сервер чата в раскрывающемся списке, но он также может настроить сервер по умолчанию в настройках. Предположим, вы уже поместили state настроек в [контекст](/learn/scaling-up-with-reducer-and-context), поэтому вы считываете `settings` из этого контекста. Теперь вы вычисляете `serverUrl` на основе выбранного сервера из пропсов и сервера по умолчанию: +Допустим, пользователь может выбрать сервер чата в выпадающем списке, но также может настроить сервер по умолчанию в настройках. Предположим, вы уже поместили состояние настроек в [контекст](/learn/scaling-up-with-reducer-and-context), поэтому вы считываете `settings` из этого контекста. Теперь вы вычисляете `serverUrl` на основе выбранного сервера из пропсов и сервера по умолчанию: ```js {3,5,10} -function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive - const settings = useContext(SettingsContext); // settings is reactive - const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl is reactive +function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен + const settings = useContext(SettingsContext); // settings реактивен + const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl реактивен useEffect(() => { - const connection = createConnection(serverUrl, roomId); // Ваш Effect считывает roomId и serverUrl + const connection = createConnection(serverUrl, roomId); // Ваш эффект считывает roomId и serverUrl connection.connect(); return () => { connection.disconnect(); }; - }, [roomId, serverUrl]); // Поэтому ему нужно повторно синхронизироваться, когда что-либо из них меняется! + }, [roomId, serverUrl]); // Поэтому ему нужно повторно синхронизироваться, когда любое из них изменится! // ... } ``` -В этом примере `serverUrl` не является пропсом или переменной state. Это обычная переменная, которую вы вычисляете во время рендеринга. Но она вычисляется во время рендеринга, поэтому она может измениться из-за повторного рендеринга. Вот почему она реактивная. +В этом примере `serverUrl` не является пропсом или переменной состояния. Это обычная переменная, которую вы вычисляете во время рендеринга. Но она вычисляется во время рендеринга, поэтому может измениться из-за повторного рендеринга. Вот почему она реактивна. -**Все значения внутри компонента (включая пропсы, state и переменные в теле вашего компонента) являются реактивными. Любое реактивное значение может измениться при повторном рендеринге, поэтому вам нужно включить реактивные значения в качестве зависимостей Effect.** +**Все значения внутри компонента (включая пропсы, состояние и переменные в теле вашего компонента) являются реактивными. Любое реактивное значение может измениться при повторном рендеринге, поэтому вам нужно включать реактивные значения в качестве зависимостей эффекта.** -Другими словами, Effects «реагируют» на все значения из тела компонента. +Другими словами, эффекты "реагируют" на все значения из тела компонента. @@ -585,17 +582,17 @@ function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive Изменяемые значения (включая глобальные переменные) не являются реактивными. -**Изменяемое значение, такое как [`location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname), не может быть зависимостью.** Оно изменяемое, поэтому может измениться в любое время полностью за пределами потока данных рендеринга React. Изменение его не вызовет повторный рендеринг вашего компонента. Поэтому, даже если вы указали его в зависимостях, React *не будет знать*, чтобы повторно синхронизировать Effect при его изменении. Это также нарушает правила React, потому что чтение изменяемых данных во время рендеринга (когда вы вычисляете зависимости) нарушает [чистоту рендеринга.](/learn/keeping-components-pure) Вместо этого вы должны считывать и подписываться на внешнее изменяемое значение с помощью [`useSyncExternalStore`.](/learn/you-might-not-need-an-effect#subscribing-to-an-external-store) +**Изменяемое значение, такое как [`location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname), не может быть зависимостью.** Оно изменяемо, поэтому может измениться в любой момент совершенно вне потока данных рендеринга React. Его изменение не вызовет повторный рендеринг вашего компонента. Следовательно, даже если вы укажете его в зависимостях, React *не узнает*, что нужно повторно синхронизировать эффект при его изменении. Это также нарушает правила React, потому что чтение изменяемых данных во время рендеринга (когда вы вычисляете зависимости) нарушает [чистоту рендеринга.](/learn/keeping-components-pure) Вместо этого вам следует считывать и подписываться на внешний изменяемый объект с помощью [`useSyncExternalStore`.](/learn/you-might-not-need-an-effect#subscribing-to-an-external-store) -**Изменяемое значение, такое как [`ref.current`](/reference/react/useRef#reference) или вещи, которые вы считываете из него, также не может быть зависимостью.** Объект ref, возвращаемый `useRef`, сам по себе может быть зависимостью, но его свойство `current` намеренно изменяемое. Это позволяет вам [отслеживать что-то, не вызывая повторный рендеринг.](/learn/referencing-values-with-refs) Но поскольку его изменение не вызывает повторный рендеринг, это не реактивное значение, и React не будет знать, чтобы повторно запустить ваш Effect при его изменении. +**Изменяемое значение, такое как [`ref.current`](/reference/react/useRef#reference) или то, что вы из него считываете, также не может быть зависимостью.** Сам объект ref, возвращаемый `useRef`, может быть зависимостью, но его свойство `current` намеренно изменяемо. Оно позволяет вам [отслеживать что-то, не вызывая повторный рендеринг.](/learn/referencing-values-with-refs) Но поскольку его изменение не вызывает повторный рендеринг, это не реактивное значение, и React не узнает, когда нужно повторно запустить ваш эффект при его изменении. -Как вы узнаете ниже на этой странице, линтер будет проверять эти проблемы автоматически. +Как вы узнаете ниже на этой странице, линтер автоматически проверит эти проблемы. -### React проверяет, что вы указали каждое реактивное значение в качестве зависимости {/*react-verifies-that-you-specified-every-reactive-value-as-a-dependency*/} +### React проверяет, что вы указали каждую реактивную переменную в качестве зависимости {/*react-verifies-that-you-specified-every-reactive-value-as-a-dependency*/} -Если ваш линтер [настроен для React,](/learn/editor-setup#linting) он проверит, что каждое реактивное значение, используемое кодом вашего Effect, объявлено в качестве его зависимости. Например, это ошибка линтинга, потому что и `roomId`, и `serverUrl` являются реактивными: +Если ваш линтер [настроен для React,](/learn/editor-setup#linting) он проверит, что каждое реактивное значение, используемое кодом вашего эффекта, объявлено в качестве его зависимости. Например, это ошибка линтинга, потому что и `roomId`, и `serverUrl` реактивны: @@ -603,14 +600,14 @@ function ChatRoom({ roomId, selectedServerUrl }) { // roomId is reactive import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; -function ChatRoom({ roomId }) { // roomId is reactive - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive +function ChatRoom({ roomId }) { // roomId реактивен + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl реактивен useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); - }, []); // <-- Что-то здесь не так! + }, []); // <-- Здесь что-то не так! return ( <> @@ -669,13 +666,13 @@ button { margin-left: 10px; } -Это может выглядеть как ошибка React, но на самом деле React указывает на ошибку в вашем коде. И `roomId`, и `serverUrl` могут меняться со временем, но вы забываете повторно синхронизировать свой Effect при их изменении. Вы останетесь подключенными к исходному `roomId` и `serverUrl` даже после того, как пользователь выберет другие значения в пользовательском интерфейсе. +Это может выглядеть как ошибка React, но на самом деле React указывает на ошибку в вашем коде. И `roomId`, и `serverUrl` могут меняться со временем, но вы забыли повторно синхронизировать ваш эффект при их изменении. Вы останетесь подключены к исходным `roomId` и `serverUrl` даже после того, как пользователь выберет другие значения в пользовательском интерфейсе. -Чтобы исправить ошибку, следуйте предложению линтера и укажите `roomId` и `serverUrl` в качестве зависимостей вашего Effect: +Чтобы исправить ошибку, следуйте предложению линтера указать `roomId` и `serverUrl` в качестве зависимостей вашего эффекта: ```js {9} -function ChatRoom({ roomId }) { // roomId is reactive - const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl is reactive +function ChatRoom({ roomId }) { // roomId реактивен + const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl реактивен useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); @@ -691,20 +688,19 @@ function ChatRoom({ roomId }) { // roomId is reactive -В некоторых случаях React *знает*, что значение никогда не меняется, даже если оно объявлено внутри компонента. Например, функция [`set`](/reference/react/useState#setstate), возвращаемая из `useState`, и объект ref, возвращаемый из [`useRef`](/reference/react/useRef), являются *стабильными* — гарантируется, что они не изменятся при повторном рендеринге. Стабильные значения не являются реактивными, поэтому вы можете опустить их из списка. Их включение разрешено: они не изменятся, поэтому это не имеет значения. +В некоторых случаях React *знает*, что значение никогда не меняется, даже если оно объявлено внутри компонента. Например, функция [`set`](/reference/react/useState#setstate), возвращаемая из `useState`, и объект ref, возвращаемый [`useRef`](/reference/react/useRef), являются *стабильными* — гарантируется, что они не изменятся при повторном рендеринге. Стабильные значения не являются реактивными, поэтому вы можете опустить их из списка. Включение их разрешено: они не изменятся, так что это не имеет значения. +### Что делать, если вы не хотите повторно синхронизировать {/*what-to-do-when-you-dont-want-to-re-synchronize*/} -### Что делать, когда вы не хотите повторной синхронизации {/*what-to-do-when-you-dont-want-to-re-synchronize*/} +В предыдущем примере вы исправили ошибку линтера, указав `roomId` и `serverUrl` в качестве зависимостей. -В предыдущем примере вы исправили ошибку линтера, перечислив `roomId` и `serverUrl` в качестве зависимостей. - -**Однако вместо этого вы можете «доказать» линтеру, что эти значения не являются реактивными,** то есть что они *не могут* измениться в результате повторного рендеринга. Например, если `serverUrl` и `roomId` не зависят от рендеринга и всегда имеют одни и те же значения, вы можете переместить их за пределы компонента. Теперь они не должны быть зависимостями: +**Однако вы можете "доказать" линтеру, что эти значения не являются реактивными,** то есть они *не могут* измениться в результате повторного рендеринга. Например, если `serverUrl` и `roomId` не зависят от рендеринга и всегда имеют одинаковые значения, вы можете вынести их за пределы компонента. Теперь они не нуждаются в указании в качестве зависимостей: ```js {1,2,11} -const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive -const roomId = 'general'; // roomId is not reactive +const serverUrl = 'https://localhost:1234'; // serverUrl не является реактивным +const roomId = 'general'; // roomId не является реактивным function ChatRoom() { useEffect(() => { @@ -718,13 +714,13 @@ function ChatRoom() { } ``` -Вы также можете переместить их *внутри Effect.* Они не вычисляются во время рендеринга, поэтому они не являются реактивными: +Вы также можете вынести их *внутрь эффекта*. Они не вычисляются во время рендеринга, поэтому не являются реактивными: ```js {3,4,10} function ChatRoom() { useEffect(() => { - const serverUrl = 'https://localhost:1234'; // serverUrl is not reactive - const roomId = 'general'; // roomId is not reactive + const serverUrl = 'https://localhost:1234'; // serverUrl не является реактивным + const roomId = 'general'; // roomId не является реактивным const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { @@ -735,21 +731,21 @@ function ChatRoom() { } ``` -**Эффекты — это реактивные блоки кода.** Они повторно синхронизируются, когда изменяются значения, которые вы читаете внутри них. В отличие от обработчиков событий, которые запускаются только один раз за взаимодействие, Эффекты запускаются всякий раз, когда необходима синхронизация. +**Эффекты — это реактивные блоки кода.** Они повторно синхронизируются при изменении значений, которые вы читаете внутри них. В отличие от обработчиков событий, которые выполняются только один раз за взаимодействие, эффекты выполняются всякий раз, когда требуется синхронизация. -**Вы не можете «выбрать» свои зависимости.** Ваши зависимости должны включать все [реактивные значения](#all-variables-declared-in-the-component-body-are-reactive), которые вы читаете в Effect. Линтер обеспечивает это. Иногда это может приводить к проблемам, таким как бесконечные циклы, и к тому, что ваш Effect слишком часто повторно синхронизируется. Не исправляйте эти проблемы, подавляя линтер! Вместо этого попробуйте следующее: +**Вы не можете "выбирать" свои зависимости.** Ваши зависимости должны включать каждое [реактивное значение](#all-variables-declared-in-the-component-body-are-reactive), которое вы читаете в эффекте. Линтер обеспечивает это. Иногда это может привести к проблемам, таким как бесконечные циклы и слишком частая повторная синхронизация эффекта. Не исправляйте эти проблемы, подавляя линтер! Вот что стоит попробовать вместо этого: -* **Убедитесь, что ваш Effect представляет собой независимый процесс синхронизации.** Если ваш Effect ничего не синхронизирует, [он может быть ненужным.](/learn/you-might-not-need-an-effect) Если он синхронизирует несколько независимых вещей, [разделите его.](#each-effect-represents-a-separate-synchronization-process) +* **Проверьте, представляет ли ваш эффект независимый процесс синхронизации.** Если ваш эффект ничего не синхронизирует, [он может быть ненужным.](/learn/you-might-not-need-an-effect) Если он синхронизирует несколько независимых вещей, [разделите его.](#each-effect-represents-a-separate-synchronization-process) -* **Если вы хотите прочитать последнее значение пропсов или состояния, не «реагируя» на него и не повторно синхронизируя Effect,** вы можете разделить свой Effect на реактивную часть (которую вы сохраните в Effect) и нереактивную часть (которую вы извлечете в то, что называется _Effect Event_). [Прочтите о разделении событий и эффектов.](/learn/separating-events-from-effects) +* **Если вы хотите прочитать последнее значение пропсов или состояния, не "реагируя" на него и не повторно синхронизируя эффект,** вы можете разделить эффект на реактивную часть (которую вы оставите в эффекте) и нереактивную часть (которую вы вынесете во что-то под названием _Событие эффекта_). [Читайте о разделении событий и эффектов.](/learn/separating-events-from-effects) -* **Избегайте полагаться на объекты и функции в качестве зависимостей.** Если вы создаете объекты и функции во время рендеринга, а затем читаете их из Effect, они будут разными при каждом рендеринге. Это приведет к повторной синхронизации вашего Effect каждый раз. [Прочтите больше об удалении ненужных зависимостей из Effects.](/learn/removing-effect-dependencies) +* **Избегайте использования объектов и функций в качестве зависимостей.** Если вы создаете объекты и функции во время рендеринга, а затем читаете их из эффекта, они будут отличаться при каждом рендеринге. Это приведет к повторной синхронизации эффекта каждый раз. [Читайте подробнее об удалении ненужных зависимостей из эффектов.](/learn/removing-effect-dependencies) -Линтер — ваш друг, но его возможности ограничены. Линтер знает только, когда зависимости *неправильные*. Он не знает *лучшего* способа решить каждый случай. Если линтер предлагает зависимость, но ее добавление вызывает цикл, это не означает, что линтером следует пренебречь. Вам нужно изменить код внутри (или за пределами) Effect, чтобы это значение не было реактивным и не *должно* было быть зависимостью. +Линтер — ваш друг, но его возможности ограничены. Линтер знает только, когда зависимости *неправильны*. Он не знает *лучшего* способа решения каждого случая. Если линтер предлагает зависимость, но ее добавление вызывает цикл, это не значит, что линтер следует игнорировать. Вам нужно изменить код внутри (или вне) эффекта так, чтобы это значение не было реактивным и *не нуждалось* быть зависимостью. -Если у вас есть существующая кодовая база, у вас могут быть некоторые Effects, которые подавляют линтер следующим образом: +Если у вас есть существующая кодовая база, у вас могут быть эффекты, которые подавляют линтер следующим образом: ```js {3-4} useEffect(() => { @@ -759,34 +755,34 @@ useEffect(() => { }, []); ``` -На [следующих](/learn/separating-events-from-effects) [страницах](/learn/removing-effect-dependencies) вы узнаете, как исправить этот код, не нарушая правил. Это всегда стоит исправить! +На [следующих](/learn/separating-events-from-effects) [страницах](/learn/removing-effect-dependencies) вы узнаете, как исправить этот код, не нарушая правил. Всегда стоит это исправлять! - Компоненты могут монтироваться, обновляться и размонтироваться. -- Каждый Effect имеет отдельный жизненный цикл от окружающего компонента. -- Каждый Effect описывает отдельный процесс синхронизации, который может *запускаться* и *останавливаться*. -- Когда вы пишете и читаете Effects, думайте с точки зрения каждого отдельного Effect (как начать и остановить синхронизацию), а не с точки зрения компонента (как он монтируется, обновляется или размонтируется). -- Значения, объявленные внутри тела компонента, являются «реактивными». -- Реактивные значения должны повторно синхронизировать Effect, потому что они могут меняться со временем. -- Линтер проверяет, что все реактивные значения, используемые внутри Effect, указаны в качестве зависимостей. -- Все ошибки, отмеченные линтером, являются законными. Всегда есть способ исправить код, чтобы не нарушать правила. +- Каждый эффект имеет отдельный жизненный цикл от окружающего компонента. +- Каждый эффект описывает отдельный процесс синхронизации, который может *начинаться* и *останавливаться*. +- При написании и чтении эффектов думайте с точки зрения каждого отдельного эффекта (как начать и остановить синхронизацию), а не с точки зрения компонента (как он монтируется, обновляется или размонтируется). +- Значения, объявленные внутри тела компонента, являются "реактивными". +- Реактивные значения должны повторно синхронизировать эффект, поскольку они могут меняться со временем. +- Линтер проверяет, что все реактивные значения, используемые внутри эффекта, указаны в качестве зависимостей. +- Все ошибки, отмеченные линтером, являются обоснованными. Всегда есть способ исправить код, чтобы не нарушать правила. -#### Исправьте повторное подключение при каждом нажатии клавиши {/*fix-reconnecting-on-every-keystroke*/} +#### Исправить повторное подключение при каждом нажатии клавиши {/*fix-reconnecting-on-every-keystroke*/} -В этом примере компонент `ChatRoom` подключается к чат-комнате при монтировании компонента, отключается при размонтировании и повторно подключается при выборе другой чат-комнаты. Это поведение корректно, поэтому вам нужно сохранить его работоспособность. +В этом примере компонент `ChatRoom` подключается к чату при монтировании компонента, отключается при размонтировании и переподключается при выборе другой комнаты чата. Это поведение корректно, поэтому вам нужно сохранить его работоспособность. -Однако есть проблема. Всякий раз, когда вы вводите текст в поле ввода сообщений внизу, `ChatRoom` *также* повторно подключается к чату. (Вы можете заметить это, очистив консоль и набрав текст в поле ввода.) Исправьте проблему, чтобы этого не происходило. +Однако есть проблема. Каждый раз, когда вы вводите текст в поле ввода сообщения внизу, `ChatRoom` *также* переподключается к чату. (Вы можете заметить это, очистив консоль и введя текст в поле ввода.) Исправьте проблему, чтобы этого не происходило. -Возможно, вам потребуется добавить массив зависимостей для этого Effect. Какие зависимости должны быть там? +Возможно, вам потребуется добавить массив зависимостей для этого эффекта. Какие зависимости там должны быть? @@ -863,7 +859,7 @@ button { margin-left: 10px; } -У этого Effect вообще не было массива зависимостей, поэтому он повторно синхронизировался после каждого повторного рендеринга. Сначала добавьте массив зависимостей. Затем убедитесь, что каждое реактивное значение, используемое Effect, указано в массиве. Например, `roomId` является реактивным (потому что это пропс), поэтому его следует включить в массив. Это гарантирует, что при выборе пользователем другой комнаты чат переподключится. С другой стороны, `serverUrl` определяется за пределами компонента. Вот почему его не нужно включать в массив. +У этого эффекта вообще не было массива зависимостей, поэтому он повторно синхронизировался после каждого рендеринга. Сначала добавьте массив зависимостей. Затем убедитесь, что каждое реактивное значение, используемое эффектом, указано в массиве. Например, `roomId` является реактивным (поскольку это пропс), поэтому он должен быть включен в массив. Это гарантирует, что при выборе пользователем другой комнаты чат переподключится. С другой стороны, `serverUrl` определен вне компонента. Поэтому его не нужно включать в массив. @@ -940,13 +936,13 @@ button { margin-left: 10px; } #### Включение и выключение синхронизации {/*switch-synchronization-on-and-off*/} -В этом примере Effect подписывается на событие [`pointermove`](https://developer.mozilla.org/ru/docs/Web/API/Element/pointermove_event) окна, чтобы переместить розовую точку на экране. Попробуйте навести курсор на область предварительного просмотра (или коснуться экрана, если вы используете мобильное устройство) и посмотрите, как розовая точка следует за вашим движением. +В этом примере эффект подписывается на событие `pointermove` окна [`pointermove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event) для перемещения розовой точки на экране. Попробуйте навести курсор на область предварительного просмотра (или прикоснуться к экрану, если вы на мобильном устройстве), и вы увидите, как розовая точка следует за вашим движением. -Также есть флажок. Установка флажка переключает переменную состояния `canMove`, но эта переменная состояния нигде не используется в коде. Ваша задача — изменить код так, чтобы, когда `canMove` имеет значение `false` (флажок снят), точка перестала двигаться. После того, как вы снова включите флажок (и установите для `canMove` значение `true`), поле снова должно следовать за движением. Другими словами, будет ли точка двигаться или нет, должно оставаться синхронизированным с тем, установлен ли флажок. +Также есть флажок. Установка флажка переключает переменную состояния `canMove`, но эта переменная состояния нигде не используется в коде. Ваша задача — изменить код так, чтобы при `canMove` равном `false` (флажок снят) точка переставала двигаться. После того как вы снова установите флажок (и установите `canMove` в `true`), точка снова будет следовать за движением. Другими словами, возможность перемещения точки должна оставаться синхронизированной с тем, отмечен ли флажок. -Вы не можете объявить Effect условно. Однако код внутри Effect может использовать условия! +Вы не можете объявлять эффект условно. Однако код внутри эффекта может использовать условия! @@ -1060,7 +1056,7 @@ body { -В качестве альтернативы вы можете обернуть логику *подписки на события* в условие `if (canMove) { ... }`: +Альтернативно, вы можете обернуть логику *подписки на событие* в условие `if (canMove) { ... }`: @@ -1116,20 +1112,19 @@ body { -В обоих этих случаях `canMove` является реактивной переменной, которую вы читаете внутри Effect. Вот почему ее необходимо указать в списке зависимостей Effect. Это гарантирует, что Effect повторно синхронизируется после каждого изменения ее значения. +В обоих этих случаях `canMove` является реактивной переменной, которую вы читаете внутри эффекта. Поэтому она должна быть указана в списке зависимостей эффекта. Это гарантирует, что эффект повторно синхронизируется после каждого изменения ее значения. +#### Исследовать ошибку устаревшего значения {/*investigate-a-stale-value-bug*/} -#### Исследуйте ошибку с устаревшим значением {/*investigate-a-stale-value-bug*/} - -В этом примере розовая точка должна двигаться, когда установлен флажок, и должна перестать двигаться, когда флажок снят. Логика для этого уже реализована: обработчик события `handleMove` проверяет переменную состояния `canMove`. +В этом примере розовая точка должна двигаться, когда флажок установлен, и переставать двигаться, когда флажок снят. Логика для этого уже реализована: обработчик события `handleMove` проверяет переменную состояния `canMove`. -Однако по какой-то причине переменная состояния `canMove` внутри `handleMove` выглядит «устаревшей»: она всегда `true`, даже после того, как вы снимете флажок. Как это возможно? Найдите ошибку в коде и исправьте ее. +Однако по какой-то причине переменная состояния `canMove` внутри `handleMove` кажется "устаревшей": она всегда равна `true`, даже после снятия флажка. Как это возможно? Найдите ошибку в коде и исправьте ее. -Если вы видите, что правило линтера подавлено, удалите подавление! Именно там обычно и находятся ошибки. +Если вы видите подавление правила линтера, удалите его! Обычно ошибки кроются именно там. @@ -1191,13 +1186,13 @@ body { -Проблема с исходным кодом заключалась в подавлении линтера зависимостей. Если вы удалите подавление, вы увидите, что этот эффект зависит от функции `handleMove`. Это имеет смысл: `handleMove` объявляется внутри тела компонента, что делает его реактивным значением. Каждое реактивное значение должно быть указано в качестве зависимости, иначе оно со временем может устареть! +Проблема исходного кода заключалась в подавлении линтера зависимостей. Если удалить подавление, вы увидите, что этот эффект зависит от функции `handleMove`. Это имеет смысл: `handleMove` объявлена внутри тела компонента, что делает ее реактивным значением. Каждое реактивное значение должно быть указано как зависимость, иначе оно потенциально может устареть со временем! -Автор исходного кода «обманул» React, заявив, что эффект не зависит (`[]`) от каких-либо реактивных значений. Вот почему React не пересинхронизировал эффект после изменения `canMove` (и `handleMove` вместе с ним). Поскольку React не пересинхронизировал эффект, `handleMove`, прикрепленный в качестве слушателя, является функцией `handleMove`, созданной во время начального рендеринга. Во время начального рендеринга `canMove` было равно `true`, поэтому `handleMove` из начального рендеринга навсегда будет видеть это значение. +Автор исходного кода "солгал" React, сказав, что эффект не зависит (`[]`) от каких-либо реактивных значений. Поэтому React не повторно синхронизировал эффект после изменения `canMove` (и вместе с ним `handleMove`). Поскольку React не повторно синхронизировал эффект, `handleMove`, прикрепленный как слушатель, является функцией `handleMove`, созданной во время первоначального рендеринга. Во время первоначального рендеринга `canMove` было `true`, поэтому `handleMove` из первоначального рендеринга навсегда увидит это значение. -**Если вы никогда не подавляете линтер, вы никогда не увидите проблем с устаревшими значениями.** Есть несколько способов решить эту ошибку, но всегда следует начинать с удаления подавления линтера. Затем измените код, чтобы исправить ошибку линтинга. +**Если вы никогда не подавляете линтер, вы никогда не столкнетесь с проблемами устаревших значений.** Существует несколько способов решить эту ошибку, но вы всегда должны начинать с удаления подавления линтера. Затем измените код, чтобы исправить ошибку линтера. -Вы можете изменить зависимости эффекта на `[handleMove]`, но поскольку это будет вновь определенная функция для каждого рендеринга, вы можете просто удалить массив зависимостей вообще. Тогда эффект *будет* пересинхронизироваться после каждого повторного рендеринга: +Вы можете изменить зависимости эффекта на `[handleMove]`, но поскольку это будет новая функция для каждого рендеринга, вы можете вообще убрать массив зависимостей. Тогда эффект *будет* повторно синхронизироваться после каждого рендеринга: @@ -1254,9 +1249,9 @@ body { -Это решение работает, но оно не идеально. Если вы поместите `console.log('Resubscribing')` внутри эффекта, вы заметите, что он переподписывается после каждого повторного рендеринга. Переподписка происходит быстро, но все равно было бы неплохо избежать этого так часто. +Это решение работает, но оно не идеально. Если вы добавите `console.log('Resubscribing')` внутрь эффекта, вы заметите, что он повторно подписывается после каждого повторного рендеринга. Повторная подписка происходит быстро, но все же было бы неплохо избежать этого так часто. -Лучшим решением было бы переместить функцию `handleMove` *внутрь* эффекта. Тогда `handleMove` не будет реактивным значением, и ваш эффект не будет зависеть от функции. Вместо этого ему нужно будет зависеть от `canMove`, которое ваш код теперь считывает внутри эффекта. Это соответствует желаемому поведению, поскольку ваш эффект теперь будет оставаться синхронизированным со значением `canMove`: +Лучшим решением будет вынести функцию `handleMove` *внутрь* эффекта. Тогда `handleMove` не будет реактивным значением, и поэтому ваш эффект не будет зависеть от функции. Вместо этого ему потребуется зависеть от `canMove`, который ваш код теперь читает изнутри эффекта. Это соответствует желаемому поведению, поскольку ваш эффект теперь будет оставаться синхронизированным со значением `canMove`: @@ -1313,21 +1308,21 @@ body { -Попробуйте добавить `console.log('Resubscribing')` внутри тела эффекта и заметьте, что теперь он переподписывается только при переключении флажка (изменение `canMove`) или редактировании кода. Это делает его лучше, чем предыдущий подход, который всегда переподписывался. +Попробуйте добавить `console.log('Resubscribing')` внутрь тела эффекта и заметьте, что теперь он повторно подписывается только при переключении флажка (`canMove` изменяется) или при редактировании кода. Это лучше, чем предыдущий подход, который всегда повторно подписывался. -Вы узнаете более общий подход к этому типу проблем в [Разделение событий и эффектов.](/learn/separating-events-from-effects) - +Вы узнаете более общий подход к этому типу проблем в разделе [Separating Events from Effects.](/learn/separating-events-from-effects) + #### Исправьте переключение соединения {/*fix-a-connection-switch*/} -В этом примере служба чата в `chat.js` предоставляет два разных API: `createEncryptedConnection` и `createUnencryptedConnection`. Корневой компонент `App` позволяет пользователю выбрать, использовать ли шифрование или нет, а затем передает соответствующий метод API дочернему компоненту `ChatRoom` в качестве пропса `createConnection`. +В этом примере сервис чата в `chat.js` предоставляет два разных API: `createEncryptedConnection` и `createUnencryptedConnection`. Корневой компонент `App` позволяет пользователю выбрать, использовать ли шифрование, а затем передает соответствующий метод API дочернему компоненту `ChatRoom` в качестве пропса `createConnection`. -Обратите внимание, что изначально в консоли отображается сообщение о том, что соединение не зашифровано. Попробуйте переключить флажок: ничего не произойдет. Однако, если после этого вы измените выбранную комнату, чат переподключится *и* включит шифрование (как вы увидите из сообщений в консоли). Это баг. Исправьте баг, чтобы переключение флажка *также* приводило к переподключению чата. +Обратите внимание, что изначально в консоли отображаются сообщения о том, что соединение не зашифровано. Попробуйте установить флажок: ничего не произойдет. Однако, если вы после этого смените выбранную комнату, чат переподключится *и* включит шифрование (как вы увидите из сообщений в консоли). Это ошибка. Исправьте ошибку так, чтобы установка флажка *также* приводила к переподключению чата. -Подавление линтера всегда подозрительно. Может ли это быть багом? +Подавление линтера всегда подозрительно. Может ли это быть ошибкой? @@ -1347,7 +1342,7 @@ export default function App() { return ( <>
-Правильно, что `createConnection` является зависимостью. Однако этот код немного хрупкий, потому что кто-то может отредактировать компонент `App`, чтобы передать встроенную функцию в качестве значения этого пропса. В этом случае его значение будет разным каждый раз, когда компонент `App` перерендеривается, поэтому эффект может пересинхронизироваться слишком часто. Чтобы избежать этого, вы можете передать `isEncrypted`: +It is correct that `createConnection` is a dependency. However, this code is a bit fragile because someone could edit the `App` component to pass an inline function as the value of this prop. In that case, its value would be different every time the `App` component re-renders, so the Effect might re-synchronize too often. To avoid this, you can pass `isEncrypted` down instead: ```js src/App.js import { useState } from 'react'; import ChatRoom from './ChatRoom.js'; +import { + createEncryptedConnection, + createUnencryptedConnection, +} from './chat.js'; export default function App() { const [roomId, setRoomId] = useState('general'); @@ -1536,7 +1535,7 @@ export default function App() { return ( <> + +
+ + + ); +} +``` + +```js src/ChatRoom.js active +import { useState, useEffect } from 'react'; + +export default function ChatRoom({ roomId, createConnection }) { useEffect(() => { - const createConnection = isEncrypted ? - createEncryptedConnection : - createUnencryptedConnection; const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); - }, [roomId, isEncrypted]); + }, [roomId, createConnection]); return

Welcome to the {roomId} room!

; } @@ -1617,18 +1709,17 @@ label { display: block; margin-bottom: 10px; }
-В этой версии компонент `App` передает логический пропс вместо функции. Внутри Effect вы решаете, какую функцию использовать. Поскольку и `createEncryptedConnection`, и `createUnencryptedConnection` объявлены за пределами компонента, они не являются реактивными и не нуждаются в зависимостях. Вы узнаете больше об этом в [Удаление зависимостей Effect.](/learn/removing-effect-dependencies) +In this version, the `App` component passes a boolean prop instead of a function. Inside the Effect, you decide which function to use. Since both `createEncryptedConnection` and `createUnencryptedConnection` are declared outside the component, they aren't reactive, and don't need to be dependencies. You'll learn more about this in [Removing Effect Dependencies.](/learn/removing-effect-dependencies) +#### Заполните цепочку выпадающих списков {/*populate-a-chain-of-select-boxes*/} -#### Заполнение цепочки выпадающих списков {/*populate-a-chain-of-select-boxes*/} - -В этом примере есть два выпадающих списка. Один выпадающий список позволяет пользователю выбрать планету. Другой выпадающий список позволяет пользователю выбрать место _на этой планете_. Второй список пока не работает. Ваша задача — сделать так, чтобы он отображал места на выбранной планете. +В этом примере есть два выпадающих списка. Один позволяет пользователю выбрать планету. Другой позволяет пользователю выбрать место *на этой планете*. Второй список пока не работает. Ваша задача — сделать так, чтобы он отображал места на выбранной планете. -Посмотрите, как работает первый выпадающий список. Он заполняет состояние `planetList` результатом вызова API `"/planets"`. ID выбранной в данный момент планеты хранится в переменной состояния `planetId`. Вам нужно найти, куда добавить дополнительный код, чтобы переменная состояния `placeList` заполнялась результатом вызова API `"/planets/" + planetId + "/places"`. +Посмотрите, как работает первый выпадающий список. Он заполняет состояние `planetList` результатом вызова API `"/planets"`. Идентификатор выбранной планеты хранится в переменной состояния `planetId`. Вам нужно найти, куда добавить дополнительный код, чтобы переменная состояния `placeList` была заполнена результатом вызова API `"/planets/" + planetId + "/places"`. -Если вы реализуете это правильно, выбор планеты должен заполнять список мест. Изменение планеты должно изменять список мест. +Если вы реализуете это правильно, выбор планеты должен заполнить список мест. Изменение планеты должно изменить список мест. @@ -1666,7 +1757,7 @@ export default function Page() { return ( <>
-

Вы собираетесь: {placeId || '???'} на {planetId || '???'}

+

You are going to: {placeId || '???'} on {planetId || '???'}

); } @@ -1778,12 +1869,12 @@ label { display: block; margin-bottom: 10px; } -Есть два независимых процесса синхронизации: +There are two independent synchronization processes: -- Первый выпадающий список синхронизирован с удаленным списком планет. -- Второй выпадающий список синхронизирован с удаленным списком мест для текущего `planetId`. +- The first select box is synchronized to the remote list of planets. +- The second select box is synchronized to the remote list of places for the current `planetId`. -Вот почему имеет смысл описывать их как два отдельных эффекта. Вот пример того, как вы можете это сделать: +This is why it makes sense to describe them as two separate Effects. Here's an example of how you could do this: @@ -1834,7 +1925,7 @@ export default function Page() { return ( <>
-

Вы собираетесь: {placeId || '???'} на {planetId || '???'}

+

You are going to: {placeId || '???'} on {planetId || '???'}

); } @@ -1944,9 +2035,9 @@ label { display: block; margin-bottom: 10px; }
-Этот код немного повторяющийся. Однако это не является веской причиной для объединения его в один эффект! Если бы вы это сделали, вам пришлось бы объединить зависимости обоих эффектов в один список, и тогда изменение планеты привело бы к повторной выборке списка всех планет. Эффекты — это не инструмент для повторного использования кода. +This code is a bit repetitive. However, that's not a good reason to combine it into a single Effect! If you did this, you'd have to combine both Effect's dependencies into one list, and then changing the planet would refetch the list of all planets. Effects are not a tool for code reuse. -Вместо этого, чтобы уменьшить повторение, вы можете извлечь некоторую логику в пользовательский хук, такой как `useSelectOptions` ниже: +Instead, to reduce repetition, you can extract some logic into a custom Hook like `useSelectOptions` below: @@ -1970,7 +2061,7 @@ export default function Page() { return ( <>
-

Вы собираетесь: {placeId || '...'} на {planetId || '...'}

+

You are going to: {placeId || '...'} on {planetId || '...'}

); } @@ -2107,7 +2198,7 @@ label { display: block; margin-bottom: 10px; }
-Проверьте вкладку `useSelectOptions.js` в песочнице, чтобы увидеть, как это работает. В идеале, большинство эффектов в вашем приложении должны в конечном итоге быть заменены пользовательскими хуками, будь то написанными вами или сообществом. Пользовательские хуки скрывают логику синхронизации, поэтому вызывающий компонент не знает об эффекте. По мере продолжения работы над своим приложением вы разработаете палитру хуков на выбор, и в конечном итоге вам не придется очень часто писать эффекты в своих компонентах. +Check the `useSelectOptions.js` tab in the sandbox to see how it works. Ideally, most Effects in your application should eventually be replaced by custom Hooks, whether written by you or by the community. Custom Hooks hide the synchronization logic, so the calling component doesn't know about the Effect. As you keep working on your app, you'll develop a palette of Hooks to choose from, and eventually you won't need to write Effects in your components very often.
From 3e3457da6572b3a7911b90add3e92cde39d0c3fb 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 18:42:12 +0000 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20translate=20`lifecycle-of-reactive-?= =?UTF-8?q?effects.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/lifecycle-of-reactive-effects.md | 264 ++++++------------ 1 file changed, 84 insertions(+), 180 deletions(-) diff --git a/src/content/learn/lifecycle-of-reactive-effects.md b/src/content/learn/lifecycle-of-reactive-effects.md index 4fa324520e..cfc260e70b 100644 --- a/src/content/learn/lifecycle-of-reactive-effects.md +++ b/src/content/learn/lifecycle-of-reactive-effects.md @@ -1,6 +1,7 @@ --- -title: 'Жизненный цикл реактивных эффектов' +title: 'Lifecycle of Reactive Effects' --- + Эффекты имеют другой жизненный цикл по сравнению с компонентами. Компоненты могут монтироваться, обновляться или размонтироваться. Эффект может только выполнять две вещи: начать синхронизацию чего-либо и позже прекратить её. Этот цикл может повторяться несколько раз, если ваш Эффект зависит от пропсов и состояния, которые меняются со временем. React предоставляет правило линтера для проверки правильности указания зависимостей вашего Эффекта. Это гарантирует, что ваш Эффект синхронизирован с последними пропсами и состоянием. @@ -9,9 +10,9 @@ title: 'Жизненный цикл реактивных эффектов' -- Чем жизненный цикл Эффекта отличается от жизненного цикла компонента -- Как рассматривать каждый отдельный Эффект в изоляции -- Когда ваш Эффект должен повторно синхронизироваться и почему +- Как жизненный цикл Эффекта отличается от жизненного цикла компонента +- Как рассматривать каждый отдельный Эффект изолированно +- Когда ваш Эффект нуждается в повторной синхронизации и почему - Как определяются зависимости вашего Эффекта - Что означает реактивность значения - Что означает пустой массив зависимостей @@ -22,7 +23,7 @@ title: 'Жизненный цикл реактивных эффектов' ## Жизненный цикл Эффекта {/*the-lifecycle-of-an-effect*/} -Каждый компонент React проходит через один и тот же жизненный цикл: +Каждый React-компонент проходит через один и тот же жизненный цикл: - Компонент _монтируется_, когда он добавляется на экран. - Компонент _обновляется_, когда он получает новые пропсы или состояние, обычно в ответ на взаимодействие. @@ -30,7 +31,7 @@ title: 'Жизненный цикл реактивных эффектов' **Это хороший способ думать о компонентах, но _не_ об Эффектах.** Вместо этого, старайтесь думать о каждом Эффекте независимо от жизненного цикла вашего компонента. Эффект описывает, как [синхронизировать внешнюю систему](/learn/synchronizing-with-effects) с текущими пропсами и состоянием. По мере изменения вашего кода, синхронизация будет требоваться чаще или реже. -Чтобы проиллюстрировать это, рассмотрим Эффект, который подключает ваш компонент к серверу чата: +Чтобы проиллюстрировать это, рассмотрим этот Эффект, подключающий ваш компонент к серверу чата: ```js const serverUrl = 'https://localhost:1234'; @@ -71,19 +72,19 @@ function ChatRoom({ roomId }) { // ... ``` -Интуитивно вы можете подумать, что React будет **начинать синхронизацию**, когда компонент монтируется, и **прекращать синхронизацию**, когда компонент размонтируется. Однако, это ещё не всё! Иногда может потребоваться **начать и прекратить синхронизацию несколько раз**, пока компонент остаётся смонтированным. +Интуитивно вы можете подумать, что React будет **начать синхронизацию**, когда ваш компонент монтируется, и **прекратить синхронизацию**, когда ваш компонент размонтируется. Однако, это еще не всё! Иногда может потребоваться **начать и прекратить синхронизацию несколько раз**, пока компонент остается смонтированным. -Давайте разберёмся, _почему_ это необходимо, _когда_ это происходит и _как_ вы можете контролировать это поведение. +Давайте разберемся, _почему_ это необходимо, _когда_ это происходит и _как_ вы можете контролировать это поведение. -Некоторые Эффекты вообще не возвращают функцию очистки. [Чаще всего](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) вы захотите её вернуть — но если нет, React будет вести себя так, как будто вы вернули пустую функцию очистки. +Некоторые Эффекты вообще не возвращают функцию очистки. [Чаще всего](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) вы захотите её вернуть — но если вы этого не сделаете, React будет вести себя так, как будто вы вернули пустую функцию очистки. ### Почему синхронизация может потребоваться более одного раза {/*why-synchronization-may-need-to-happen-more-than-once*/} -Представьте, что компонент `ChatRoom` получает пропс `roomId`, который пользователь выбирает из выпадающего списка. Допустим, изначально пользователь выбирает комнату `"general"` в качестве `roomId`. Ваше приложение отображает чат комнаты `"general"`: +Представьте, что этот компонент `ChatRoom` получает пропс `roomId`, который пользователь выбирает в выпадающем списке. Допустим, изначально пользователь выбирает комнату `"general"` в качестве `roomId`. Ваше приложение отображает чат комнаты `"general"`: ```js {3} const serverUrl = 'https://localhost:1234'; @@ -119,7 +120,7 @@ function ChatRoom({ roomId /* "travel" */ }) { } ``` -Подумайте, что должно произойти дальше. Пользователь видит, что `"travel"` — это выбранная комната чата в UI. Однако, Эффект, который был запущен в прошлый раз, всё ещё подключён к комнате `"general"`. **Пропс `roomId` изменился, поэтому то, что ваш Эффект сделал тогда (подключение к комнате `"general"`), больше не соответствует UI.** +Подумайте, что должно произойти дальше. Пользователь видит, что `"travel"` — это выбранная комната чата в UI. Однако, Эффект, который выполнился в прошлый раз, всё ещё подключен к комнате `"general"`. **Пропс `roomId` изменился, поэтому то, что ваш Эффект сделал тогда (подключение к комнате `"general"`), больше не соответствует UI.** В этот момент вы хотите, чтобы React сделал две вещи: @@ -130,7 +131,7 @@ function ChatRoom({ roomId /* "travel" */ }) { ### Как React повторно синхронизирует ваш Эффект {/*how-react-re-synchronizes-your-effect*/} -Вспомните, что ваш компонент `ChatRoom` получил новое значение для своего пропса `roomId`. Раньше это было `"general"`, а теперь `"travel"`. React должен повторно синхронизировать ваш Эффект, чтобы переподключить вас к другой комнате. +Напомним, что ваш компонент `ChatRoom` получил новое значение для своего пропса `roomId`. Раньше это было `"general"`, а теперь `"travel"`. React должен повторно синхронизировать ваш Эффект, чтобы переподключить вас к другой комнате. Чтобы **прекратить синхронизацию,** React вызовет функцию очистки, которую ваш Эффект вернул после подключения к комнате `"general"`. Поскольку `roomId` был `"general"`, функция очистки отключается от комнаты `"general"`: @@ -145,7 +146,7 @@ function ChatRoom({ roomId /* "general" */ }) { // ... ``` -Затем React запустит Эффект, который вы предоставили во время этого рендера. На этот раз `roomId` будет `"travel"`, поэтому он **начнёт синхронизацию** с комнатой `"travel"` (до тех пор, пока его функция очистки не будет вызвана). +Затем React запустит Эффект, который вы предоставили во время этого рендера. На этот раз `roomId` будет `"travel"`, поэтому он **начнет синхронизацию** с комнатой чата `"travel"` (до тех пор, пока его функция очистки в конечном итоге не будет вызвана). ```js {3,4} function ChatRoom({ roomId /* "travel" */ }) { @@ -157,13 +158,13 @@ function ChatRoom({ roomId /* "travel" */ }) { Благодаря этому вы теперь подключены к той же комнате, которую выбрал пользователь в UI. Катастрофа предотвращена! -Каждый раз после повторного рендера вашего компонента с изменённым `roomId`, ваш Эффект будет повторно синхронизироваться. Например, скажем, пользователь меняет `roomId` с `"travel"` на `"music"`. React снова **прекратит синхронизацию** вашего Эффекта, вызвав его функцию очистки (отключив вас от комнаты `"travel"`). Затем он снова **начнёт синхронизацию**, запустив тело Эффекта с новым пропсом `roomId` (подключив вас к комнате `"music"`). +Каждый раз после повторного рендера вашего компонента с измененным `roomId`, ваш Эффект будет повторно синхронизироваться. Например, скажем, пользователь меняет `roomId` с `"travel"` на `"music"`. React снова **прекратит синхронизацию** вашего Эффекта, вызвав его функцию очистки (отключив вас от комнаты `"travel"`). Затем он снова **начнет синхронизацию**, запустив тело Эффекта с новым пропсом `roomId` (подключив вас к комнате `"music"`). -Наконец, когда пользователь переходит на другой экран, `ChatRoom` размонтируется. Теперь нет необходимости оставаться подключённым. React **прекратит синхронизацию** вашего Эффекта в последний раз и отключит вас от комнаты `"music"`. +Наконец, когда пользователь переходит на другой экран, `ChatRoom` размонтируется. Теперь нет необходимости оставаться подключенным. React **прекратит синхронизацию** вашего Эффекта в последний раз и отключит вас от чат-комнаты `"music"`. ### Мышление с точки зрения Эффекта {/*thinking-from-the-effects-perspective*/} -Подведём итог всему, что произошло с точки зрения компонента `ChatRoom`: +Подведем итог всему, что произошло с точки зрения компонента `ChatRoom`: 1. `ChatRoom` смонтировался с `roomId`, установленным в `"general"` 1. `ChatRoom` обновился с `roomId`, установленным в `"travel"` @@ -177,7 +178,7 @@ function ChatRoom({ roomId /* "travel" */ }) { 1. Ваш Эффект отключился от комнаты `"travel"` и подключился к комнате `"music"` 1. Ваш Эффект отключился от комнаты `"music"` -Теперь давайте подумаем о том, что произошло с точки зрения самого Эффекта: +Теперь давайте подумаем, что произошло с точки зрения самого Эффекта: ```js useEffect(() => { @@ -193,15 +194,15 @@ function ChatRoom({ roomId /* "travel" */ }) { Структура этого кода может вдохновить вас увидеть, что произошло, как последовательность непересекающихся временных периодов: -1. Ваш Эффект подключился к комнате `"general"` (до отключения) -1. Ваш Эффект подключился к комнате `"travel"` (до отключения) -1. Ваш Эффект подключился к комнате `"music"` (до отключения) +1. Ваш Эффект подключился к комнате `"general"` (до тех пор, пока не отключился) +1. Ваш Эффект подключился к комнате `"travel"` (до тех пор, пока не отключился) +1. Ваш Эффект подключился к комнате `"music"` (до тех пор, пока не отключился) -Раньше вы думали с точки зрения компонента. Когда вы смотрели с точки зрения компонента, было заманчиво думать об Эффектах как о "колбэках" или "событиях жизненного цикла", которые срабатывают в определённое время, например, "после рендера" или "перед размонтированием". Такой способ мышления очень быстро усложняется, поэтому его лучше избегать. +Ранее вы думали с точки зрения компонента. Когда вы смотрели с точки зрения компонента, было заманчиво думать об Эффектах как о "колбэках" или "событиях жизненного цикла", которые срабатывают в определенное время, например, "после рендера" или "перед размонтированием". Такой способ мышления очень быстро усложняется, поэтому его лучше избегать. -**Вместо этого, всегда фокусируйтесь на одном цикле запуска/остановки за раз. Не должно иметь значения, монтируется ли компонент, обновляется или размонтируется. Всё, что вам нужно сделать, это описать, как начать синхронизацию и как её прекратить. Если вы сделаете это хорошо, ваш Эффект будет устойчив к запуску и остановке столько раз, сколько потребуется.** +**Вместо этого, всегда фокусируйтесь на одном цикле старт/стоп за раз. Не должно иметь значения, монтируется ли компонент, обновляется или размонтируется. Всё, что вам нужно сделать, это описать, как начать синхронизацию и как её прекратить. Если вы сделаете это хорошо, ваш Эффект будет устойчив к запуску и остановке столько раз, сколько потребуется.** -Это может напомнить вам, как вы не задумываетесь, монтируется или обновляется компонент, когда пишете логику рендеринга, создающую JSX. Вы описываете, что должно быть на экране, а React [сам всё выясняет.](/learn/reacting-to-input-with-state) +Это может напомнить вам, как вы не задумываетесь, монтируется компонент или обновляется, когда пишете логику рендеринга, создающую JSX. Вы описываете, что должно быть на экране, а React [сам всё выясняет.](/learn/reacting-to-input-with-state) ### Как React проверяет, что ваш Эффект может повторно синхронизироваться {/*how-react-verifies-that-your-effect-can-re-synchronize*/} @@ -271,7 +272,7 @@ button { margin-left: 10px; } -Обратите внимание, что при первом монтировании компонента вы увидите три лога: +Обратите внимание, что когда компонент монтируется в первый раз, вы видите три лога: 1. `✅ Connecting to "general" room at https://localhost:1234...` *(только в режиме разработки)* 1. `❌ Disconnected from "general" room at https://localhost:1234.` *(только в режиме разработки)* @@ -279,13 +280,13 @@ button { margin-left: 10px; } Первые два лога — это только для режима разработки. В режиме разработки React всегда повторно монтирует каждый компонент один раз. -**React проверяет, что ваш Эффект может повторно синхронизироваться, заставляя его сделать это немедленно в режиме разработки.** Это может напомнить вам, как вы открываете и закрываете дверь лишний раз, чтобы проверить, работает ли замок. React запускает и останавливает ваш Эффект один раз дополнительно в режиме разработки, чтобы проверить, [хорошо ли вы реализовали его очистку.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) +**React проверяет, что ваш Эффект может повторно синхронизироваться, принудительно выполняя это немедленно в режиме разработки.** Это может напомнить вам, как вы открываете и закрываете дверь один лишний раз, чтобы проверить, работает ли замок. React запускает и останавливает ваш Эффект один раз дополнительно в режиме разработки, чтобы проверить, [хорошо ли вы реализовали его очистку.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) -Основная причина, по которой ваш Эффект будет повторно синхронизироваться на практике, заключается в изменении каких-либо данных, которые он использует. В песочнице выше измените выбранную комнату чата. Обратите внимание, как при изменении `roomId` ваш Эффект повторно синхронизируется. +Основная причина, по которой ваш Эффект будет повторно синхронизироваться на практике, заключается в изменении каких-либо используемых данных. В песочнице выше измените выбранную комнату чата. Обратите внимание, как при изменении `roomId` ваш Эффект повторно синхронизируется. -Однако существуют и более редкие случаи, когда повторная синхронизация необходима. Например, попробуйте отредактировать `serverUrl` в песочнице выше, пока чат открыт. Обратите внимание, как Эффект повторно синхронизируется в ответ на ваши правки кода. В будущем React может добавить новые функции, которые полагаются на повторную синхронизацию. +Однако существуют и более редкие случаи, когда повторная синхронизация необходима. Например, попробуйте отредактировать `serverUrl` в песочнице выше, пока чат открыт. Обратите внимание, как Эффект повторно синхронизируется в ответ на ваши правки кода. В будущем React может добавить новые функции, которые будут полагаться на повторную синхронизацию. -### Как React узнаёт, что нужно повторно синхронизировать Эффект {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} +### Как React узнает, что нужно повторно синхронизировать Эффект {/*how-react-knows-that-it-needs-to-re-synchronize-the-effect*/} Вы можете задаться вопросом, как React узнал, что ваш Эффект нуждается в повторной синхронизации после изменения `roomId`. Это потому, что *вы сказали React*, что его код зависит от `roomId`, включив его в [список зависимостей:](/learn/synchronizing-with-effects#step-2-specify-the-effect-dependencies) @@ -297,7 +298,7 @@ function ChatRoom({ roomId }) { // Пропс roomId может меняться return () => { connection.disconnect(); }; - }, [roomId]); // Поэтому вы говорите React, что этот Эффект "зависит от" roomId + }, [roomId]); // Таким образом, вы сообщаете React, что этот Эффект "зависит от" roomId // ... ``` @@ -309,11 +310,11 @@ function ChatRoom({ roomId }) { // Пропс roomId может меняться Каждый раз после повторного рендера вашего компонента React будет смотреть на массив зависимостей, который вы передали. Если какое-либо из значений в массиве отличается от значения в том же месте, которое вы передали во время предыдущего рендера, React повторно синхронизирует ваш Эффект. -Например, если вы передали `["general"]` во время начального рендера, а позже передали `["travel"]` во время следующего рендера, React сравнит `"general"` и `"travel"`. Это разные значения (сравнение происходит с помощью [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), поэтому React повторно синхронизирует ваш Эффект. С другой стороны, если ваш компонент повторно рендерится, но `roomId` не изменился, ваш Эффект останется подключённым к той же комнате. +Например, если вы передали `["general"]` во время начального рендера, а затем позже передали `["travel"]` во время следующего рендера, React сравнит `"general"` и `"travel"`. Это разные значения (сравнение с использованием [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), поэтому React повторно синхронизирует ваш Эффект. С другой стороны, если ваш компонент повторно рендерится, но `roomId` не изменился, ваш Эффект останется подключенным к той же комнате. ### Каждый Эффект представляет отдельный процесс синхронизации {/*each-effect-represents-a-separate-synchronization-process*/} -Воздержитесь от добавления несвязанной логики в ваш Эффект только потому, что эта логика должна выполняться одновременно с уже написанным вами Эффектом. Например, скажем, вы хотите отправлять аналитическое событие при посещении пользователем комнаты. У вас уже есть Эффект, зависящий от `roomId`, поэтому вы можете почувствовать искушение добавить туда вызов аналитики: +Сопротивляйтесь добавлению несвязанной логики в ваш Эффект только потому, что эта логика должна выполняться одновременно с Эффектом, который вы уже написали. Например, предположим, вы хотите отправить аналитическое событие при посещении пользователем комнаты. У вас уже есть Эффект, зависящий от `roomId`, поэтому вы можете почувствовать искушение добавить вызов аналитики туда: ```js {3} function ChatRoom({ roomId }) { @@ -329,7 +330,7 @@ function ChatRoom({ roomId }) { } ``` -Но представьте, что позже вы добавите другую зависимость к этому Эффекту, которая потребует переустановки соединения. Если этот Эффект повторно синхронизируется, он также вызовет `logVisit(roomId)` для той же комнаты, чего вы не предполагали. Логирование посещения — это **отдельный процесс** от подключения. Напишите их как два отдельных Эффекта: +Но представьте, что вы позже добавите другую зависимость к этому Эффекту, которая требует повторного установления соединения. Если этот Эффект повторно синхронизируется, он также вызовет `logVisit(roomId)` для той же комнаты, чего вы не предполагали. Логирование посещения — это **отдельный процесс** от подключения. Напишите их как два отдельных Эффекта: ```js {2-4} function ChatRoom({ roomId }) { @@ -345,13 +346,13 @@ function ChatRoom({ roomId }) { } ``` -**Каждый Эффект в вашем коде должен представлять отдельный и независимый процесс синхронизации.** +**Каждый Эффект в вашем коде должен представлять собой отдельный и независимый процесс синхронизации.** -В приведённом выше примере удаление одного Эффекта не нарушит логику другого. Это хороший признак того, что они синхронизируют разные вещи, и поэтому имело смысл их разделить. С другой стороны, если вы разделите связный фрагмент логики на отдельные Эффекты, код может выглядеть "чище", но будет [сложнее в поддержке.](/learn/you-might-not-need-an-effect#chains-of-computations) Вот почему вы должны думать, являются ли процессы одинаковыми или разными, а не о том, выглядит ли код чище. +В приведенном выше примере удаление одного Эффекта не нарушило бы логику другого Эффекта. Это хорошее указание на то, что они синхронизируют разные вещи, и поэтому имело смысл разделить их. С другой стороны, если вы разделите единый блок логики на отдельные Эффекты, код может выглядеть "чище", но будет [сложнее в поддержке.](/learn/you-might-not-need-an-effect#chains-of-computations) Вот почему вы должны думать, являются ли процессы одинаковыми или разными, а не о том, выглядит ли код чище. -## Эффекты "реагируют" на реактивные значения {/*effects-react-to-reactive-values*/} +## Эффекты «реагируют» на реактивные значения {/*effects-react-to-reactive-values*/} -Ваш эффект считывает две переменные (`serverUrl` и `roomId`), но вы указали `roomId` в качестве зависимости: +Ваш эффект считывает две переменные (`serverUrl` и `roomId`), но вы указали только `roomId` в качестве зависимости: ```js {5,10} const serverUrl = 'https://localhost:1234'; @@ -368,16 +369,16 @@ function ChatRoom({ roomId }) { } ``` -Почему `serverUrl` не нужно указывать в зависимостях? +Почему `serverUrl` не нужно указывать как зависимость? -Это связано с тем, что `serverUrl` никогда не меняется из-за повторного рендеринга. Он всегда одинаков, независимо от того, сколько раз компонент повторно рендерится и почему. Поскольку `serverUrl` никогда не меняется, указывать его в качестве зависимости не имеет смысла. В конце концов, зависимости делают что-то только тогда, когда они меняются со временем! +Это связано с тем, что `serverUrl` никогда не меняется из-за повторного рендеринга. Он всегда одинаков, независимо от того, сколько раз компонент рендерится и почему. Поскольку `serverUrl` никогда не меняется, указывать его как зависимость не имеет смысла. В конце концов, зависимости делают что-то только тогда, когда они меняются со временем! С другой стороны, `roomId` может быть разным при повторном рендеринге. **Пропсы, состояние и другие значения, объявленные внутри компонента, являются _реактивными_, потому что они вычисляются во время рендеринга и участвуют в потоке данных React.** Если бы `serverUrl` был переменной состояния, он был бы реактивным. Реактивные значения должны быть включены в зависимости: ```js {2,5,10} -function ChatRoom({ roomId }) { // roomId может меняться со временем +function ChatRoom({ roomId }) { // roomId меняется со временем const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl может меняться со временем useEffect(() => { @@ -467,7 +468,7 @@ button { margin-left: 10px; } -Всякий раз, когда вы изменяете реактивное значение, такое как `roomId` или `serverUrl`, эффект переподключается к серверу чата. +При изменении реактивного значения, такого как `roomId` или `serverUrl`, эффект переподключается к серверу чата. ### Что означает эффект с пустыми зависимостями {/*what-an-effect-with-empty-dependencies-means*/} @@ -491,7 +492,7 @@ function ChatRoom() { Теперь код вашего эффекта не использует *никаких* реактивных значений, поэтому его зависимости могут быть пустыми (`[]`). -С точки зрения компонента, пустой массив зависимостей `[]` означает, что эффект подключается к комнате чата только при монтировании компонента и отключается только при размонтировании компонента. (Имейте в виду, что React все равно [повторно синхронизирует его один раз дополнительно](#how-react-verifies-that-your-effect-can-re-synchronize) в режиме разработки, чтобы протестировать вашу логику.) +С точки зрения компонента, пустой массив зависимостей `[]` означает, что этот эффект подключается к комнате чата только при монтировании компонента и отключается только при размонтировании компонента. (Имейте в виду, что React все равно [повторно синхронизирует его один раз дополнительно](#how-react-verifies-that-your-effect-can-re-synchronize) в режиме разработки, чтобы протестировать вашу логику.) @@ -547,13 +548,13 @@ button { margin-left: 10px; } -Однако, если вы [думаете с точки зрения эффекта](#thinking-from-the-effects-perspective), вам вообще не нужно думать о монтировании и размонтировании. Важно то, что вы указали, как ваш эффект начинает и прекращает синхронизацию. Сегодня у него нет реактивных зависимостей. Но если вы когда-нибудь захотите, чтобы пользователь со временем изменял `roomId` или `serverUrl` (и они станут реактивными), код вашего эффекта не изменится. Вам нужно будет только добавить их в зависимости. +Однако, если вы [думаете с точки зрения эффекта](#thinking-from-the-effects-perspective), вам не нужно думать о монтировании и размонтировании вообще. Важно то, что вы указали, что делает ваш эффект для начала и прекращения синхронизации. Сегодня у него нет реактивных зависимостей. Но если вы когда-нибудь захотите, чтобы пользователь менял `roomId` или `serverUrl` со временем (и они станут реактивными), код вашего эффекта не изменится. Вам нужно будет только добавить их в зависимости. ### Все переменные, объявленные в теле компонента, реактивны {/*all-variables-declared-in-the-component-body-are-reactive*/} -Пропсы и состояние — это не единственные реактивные значения. Значения, которые вы вычисляете из них, также реактивны. Если пропсы или состояние изменяются, ваш компонент будет повторно рендериться, и значения, вычисленные из них, также изменятся. Вот почему все переменные из тела компонента, используемые эффектом, должны быть в списке зависимостей эффекта. +Пропсы и состояние — это не единственные реактивные значения. Значения, которые вы вычисляете из них, также реактивны. Если пропсы или состояние изменятся, ваш компонент отрендерится, и значения, вычисленные из них, также изменятся. Вот почему все переменные из тела компонента, используемые эффектом, должны быть в списке зависимостей эффекта. -Допустим, пользователь может выбрать сервер чата в выпадающем списке, но также может настроить сервер по умолчанию в настройках. Предположим, вы уже поместили состояние настроек в [контекст](/learn/scaling-up-with-reducer-and-context), поэтому вы считываете `settings` из этого контекста. Теперь вы вычисляете `serverUrl` на основе выбранного сервера из пропсов и сервера по умолчанию: +Допустим, пользователь может выбрать чат-сервер в выпадающем списке, но также может настроить сервер по умолчанию в настройках. Предположим, вы уже поместили состояние настроек в [контекст](/learn/scaling-up-with-reducer-and-context), поэтому вы считываете `settings` из этого контекста. Теперь вы вычисляете `serverUrl` на основе выбранного сервера из пропсов и сервера по умолчанию: ```js {3,5,10} function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен @@ -565,7 +566,7 @@ function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен return () => { connection.disconnect(); }; - }, [roomId, serverUrl]); // Поэтому ему нужно повторно синхронизироваться, когда любое из них изменится! + }, [roomId, serverUrl]); // Поэтому ему нужно повторно синхронизироваться при изменении любого из них! // ... } ``` @@ -574,7 +575,7 @@ function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен **Все значения внутри компонента (включая пропсы, состояние и переменные в теле вашего компонента) являются реактивными. Любое реактивное значение может измениться при повторном рендеринге, поэтому вам нужно включать реактивные значения в качестве зависимостей эффекта.** -Другими словами, эффекты "реагируют" на все значения из тела компонента. +Другими словами, эффекты «реагируют» на все значения из тела компонента. @@ -584,7 +585,7 @@ function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен **Изменяемое значение, такое как [`location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname), не может быть зависимостью.** Оно изменяемо, поэтому может измениться в любой момент совершенно вне потока данных рендеринга React. Его изменение не вызовет повторный рендеринг вашего компонента. Следовательно, даже если вы укажете его в зависимостях, React *не узнает*, что нужно повторно синхронизировать эффект при его изменении. Это также нарушает правила React, потому что чтение изменяемых данных во время рендеринга (когда вы вычисляете зависимости) нарушает [чистоту рендеринга.](/learn/keeping-components-pure) Вместо этого вам следует считывать и подписываться на внешний изменяемый объект с помощью [`useSyncExternalStore`.](/learn/you-might-not-need-an-effect#subscribing-to-an-external-store) -**Изменяемое значение, такое как [`ref.current`](/reference/react/useRef#reference) или то, что вы из него считываете, также не может быть зависимостью.** Сам объект ref, возвращаемый `useRef`, может быть зависимостью, но его свойство `current` намеренно изменяемо. Оно позволяет вам [отслеживать что-то, не вызывая повторный рендеринг.](/learn/referencing-values-with-refs) Но поскольку его изменение не вызывает повторный рендеринг, это не реактивное значение, и React не узнает, когда нужно повторно запустить ваш эффект при его изменении. +**Изменяемое значение, такое как [`ref.current`](/reference/react/useRef#reference) или то, что вы из него считываете, также не может быть зависимостью.** Сам объект ref, возвращаемый `useRef`, может быть зависимостью, но его свойство `current` намеренно изменяемо. Оно позволяет вам [отслеживать что-то, не вызывая повторный рендеринг.](/learn/referencing-values-with-refs) Но поскольку его изменение не вызывает повторный рендеринг, это не реактивное значение, и React не узнает, что нужно повторно запустить ваш эффект при его изменении. Как вы узнаете ниже на этой странице, линтер автоматически проверит эти проблемы. @@ -592,7 +593,7 @@ function ChatRoom({ roomId, selectedServerUrl }) { // roomId реактивен ### React проверяет, что вы указали каждую реактивную переменную в качестве зависимости {/*react-verifies-that-you-specified-every-reactive-value-as-a-dependency*/} -Если ваш линтер [настроен для React,](/learn/editor-setup#linting) он проверит, что каждое реактивное значение, используемое кодом вашего эффекта, объявлено в качестве его зависимости. Например, это ошибка линтинга, потому что и `roomId`, и `serverUrl` реактивны: +Если ваш линтер [настроен для React,](/learn/editor-setup#linting) он проверит, что каждое реактивное значение, используемое кодом вашего эффекта, объявлено как его зависимость. Например, это ошибка линтера, потому что и `roomId`, и `serverUrl` являются реактивными: @@ -688,15 +689,15 @@ function ChatRoom({ roomId }) { // roomId реактивен -В некоторых случаях React *знает*, что значение никогда не меняется, даже если оно объявлено внутри компонента. Например, функция [`set`](/reference/react/useState#setstate), возвращаемая из `useState`, и объект ref, возвращаемый [`useRef`](/reference/react/useRef), являются *стабильными* — гарантируется, что они не изменятся при повторном рендеринге. Стабильные значения не являются реактивными, поэтому вы можете опустить их из списка. Включение их разрешено: они не изменятся, так что это не имеет значения. +В некоторых случаях React *знает*, что значение никогда не меняется, даже если оно объявлено внутри компонента. Например, функция [`set`](/reference/react/useState#setstate), возвращаемая `useState`, и объект ref, возвращаемый [`useRef`](/reference/react/useRef), являются *стабильными* — гарантируется, что они не изменятся при повторном рендеринге. Стабильные значения не являются реактивными, поэтому вы можете опустить их из списка. Включение их разрешено: они не изменятся, так что это не имеет значения. ### Что делать, если вы не хотите повторно синхронизировать {/*what-to-do-when-you-dont-want-to-re-synchronize*/} -В предыдущем примере вы исправили ошибку линтера, указав `roomId` и `serverUrl` в качестве зависимостей. +В предыдущем примере вы исправили ошибку линтера, перечислив `roomId` и `serverUrl` в качестве зависимостей. -**Однако вы можете "доказать" линтеру, что эти значения не являются реактивными,** то есть они *не могут* измениться в результате повторного рендеринга. Например, если `serverUrl` и `roomId` не зависят от рендеринга и всегда имеют одинаковые значения, вы можете вынести их за пределы компонента. Теперь они не нуждаются в указании в качестве зависимостей: +**Однако вы могли бы вместо этого «доказать» линтеру, что эти значения не являются реактивными,** то есть они *не могут* измениться в результате повторного рендеринга. Например, если `serverUrl` и `roomId` не зависят от рендеринга и всегда имеют одинаковые значения, вы можете вынести их за пределы компонента. Теперь они не нуждаются в зависимостях: ```js {1,2,11} const serverUrl = 'https://localhost:1234'; // serverUrl не является реактивным @@ -733,19 +734,19 @@ function ChatRoom() { **Эффекты — это реактивные блоки кода.** Они повторно синхронизируются при изменении значений, которые вы читаете внутри них. В отличие от обработчиков событий, которые выполняются только один раз за взаимодействие, эффекты выполняются всякий раз, когда требуется синхронизация. -**Вы не можете "выбирать" свои зависимости.** Ваши зависимости должны включать каждое [реактивное значение](#all-variables-declared-in-the-component-body-are-reactive), которое вы читаете в эффекте. Линтер обеспечивает это. Иногда это может привести к проблемам, таким как бесконечные циклы и слишком частая повторная синхронизация эффекта. Не исправляйте эти проблемы, подавляя линтер! Вот что стоит попробовать вместо этого: +**Вы не можете «выбирать» свои зависимости.** Ваши зависимости должны включать каждое [реактивное значение](#all-variables-declared-in-the-component-body-are-reactive), которое вы читаете в эффекте. Линтер обеспечивает это. Иногда это может привести к проблемам, таким как бесконечные циклы, и к слишком частой повторной синхронизации эффекта. Не исправляйте эти проблемы, подавляя линтер! Вот что стоит попробовать вместо этого: * **Проверьте, представляет ли ваш эффект независимый процесс синхронизации.** Если ваш эффект ничего не синхронизирует, [он может быть ненужным.](/learn/you-might-not-need-an-effect) Если он синхронизирует несколько независимых вещей, [разделите его.](#each-effect-represents-a-separate-synchronization-process) -* **Если вы хотите прочитать последнее значение пропсов или состояния, не "реагируя" на него и не повторно синхронизируя эффект,** вы можете разделить эффект на реактивную часть (которую вы оставите в эффекте) и нереактивную часть (которую вы вынесете во что-то под названием _Событие эффекта_). [Читайте о разделении событий и эффектов.](/learn/separating-events-from-effects) +* **Если вы хотите прочитать последнее значение пропсов или состояния, не «реагируя» на него и не повторно синхронизируя эффект,** вы можете разделить эффект на реактивную часть (которую вы оставите в эффекте) и нереактивную часть (которую вы вынесете в нечто под названием _событие эффекта_). [Прочтите о разделении событий и эффектов.](/learn/separating-events-from-effects) -* **Избегайте использования объектов и функций в качестве зависимостей.** Если вы создаете объекты и функции во время рендеринга, а затем читаете их из эффекта, они будут отличаться при каждом рендеринге. Это приведет к повторной синхронизации эффекта каждый раз. [Читайте подробнее об удалении ненужных зависимостей из эффектов.](/learn/removing-effect-dependencies) +* **Избегайте использования объектов и функций в качестве зависимостей.** Если вы создаете объекты и функции во время рендеринга, а затем читаете их из эффекта, они будут разными при каждом рендеринге. Это приведет к повторной синхронизации эффекта каждый раз. [Узнайте больше об удалении ненужных зависимостей из эффектов.](/learn/removing-effect-dependencies) -Линтер — ваш друг, но его возможности ограничены. Линтер знает только, когда зависимости *неправильны*. Он не знает *лучшего* способа решения каждого случая. Если линтер предлагает зависимость, но ее добавление вызывает цикл, это не значит, что линтер следует игнорировать. Вам нужно изменить код внутри (или вне) эффекта так, чтобы это значение не было реактивным и *не нуждалось* быть зависимостью. +Линтер — ваш друг, но его возможности ограничены. Линтер знает только, когда зависимости *неправильные*. Он не знает *лучшего* способа решения каждого случая. Если линтер предлагает зависимость, но ее добавление вызывает цикл, это не значит, что линтер следует игнорировать. Вам нужно изменить код внутри (или вне) эффекта так, чтобы это значение не было реактивным и не *требовало* быть зависимостью. -Если у вас есть существующая кодовая база, у вас могут быть эффекты, которые подавляют линтер следующим образом: +Если у вас есть существующая кодовая база, у вас могут быть эффекты, которые подавляют линтер, например: ```js {3-4} useEffect(() => { @@ -755,7 +756,7 @@ useEffect(() => { }, []); ``` -На [следующих](/learn/separating-events-from-effects) [страницах](/learn/removing-effect-dependencies) вы узнаете, как исправить этот код, не нарушая правил. Всегда стоит это исправлять! +На [следующих](/learn/separating-events-from-effects) [страницах](/learn/removing-effect-dependencies) вы узнаете, как исправить этот код, не нарушая правил. Всегда стоит исправлять! @@ -763,9 +764,9 @@ useEffect(() => { - Компоненты могут монтироваться, обновляться и размонтироваться. - Каждый эффект имеет отдельный жизненный цикл от окружающего компонента. -- Каждый эффект описывает отдельный процесс синхронизации, который может *начинаться* и *останавливаться*. -- При написании и чтении эффектов думайте с точки зрения каждого отдельного эффекта (как начать и остановить синхронизацию), а не с точки зрения компонента (как он монтируется, обновляется или размонтируется). -- Значения, объявленные внутри тела компонента, являются "реактивными". +- Каждый эффект описывает отдельный процесс синхронизации, который может *начаться* и *закончиться*. +- При написании и чтении эффектов думайте с точки зрения каждого отдельного эффекта (как начать и закончить синхронизацию), а не с точки зрения компонента (как он монтируется, обновляется или размонтируется). +- Значения, объявленные внутри тела компонента, являются «реактивными». - Реактивные значения должны повторно синхронизировать эффект, поскольку они могут меняться со временем. - Линтер проверяет, что все реактивные значения, используемые внутри эффекта, указаны в качестве зависимостей. - Все ошибки, отмеченные линтером, являются обоснованными. Всегда есть способ исправить код, чтобы не нарушать правила. @@ -778,11 +779,11 @@ useEffect(() => { В этом примере компонент `ChatRoom` подключается к чату при монтировании компонента, отключается при размонтировании и переподключается при выборе другой комнаты чата. Это поведение корректно, поэтому вам нужно сохранить его работоспособность. -Однако есть проблема. Каждый раз, когда вы вводите текст в поле ввода сообщения внизу, `ChatRoom` *также* переподключается к чату. (Вы можете заметить это, очистив консоль и введя текст в поле ввода.) Исправьте проблему, чтобы этого не происходило. +Однако есть проблема. Каждый раз, когда вы печатаете в поле ввода сообщения внизу, `ChatRoom` *также* переподключается к чату. (Вы можете заметить это, очистив консоль и напечатав что-нибудь во входном поле.) Исправьте проблему так, чтобы этого не происходило. -Возможно, вам потребуется добавить массив зависимостей для этого эффекта. Какие зависимости там должны быть? +Возможно, вам понадобится добавить массив зависимостей для этого эффекта. Какие зависимости там должны быть? @@ -936,9 +937,9 @@ button { margin-left: 10px; } #### Включение и выключение синхронизации {/*switch-synchronization-on-and-off*/} -В этом примере эффект подписывается на событие `pointermove` окна [`pointermove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event) для перемещения розовой точки на экране. Попробуйте навести курсор на область предварительного просмотра (или прикоснуться к экрану, если вы на мобильном устройстве), и вы увидите, как розовая точка следует за вашим движением. +В этом примере эффект подписывается на событие `pointermove` окна [`pointermove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event), чтобы перемещать розовую точку на экране. Попробуйте навести курсор на область предварительного просмотра (или прикоснуться к экрану, если вы находитесь на мобильном устройстве) и посмотрите, как розовая точка следует за вашим движением. -Также есть флажок. Установка флажка переключает переменную состояния `canMove`, но эта переменная состояния нигде не используется в коде. Ваша задача — изменить код так, чтобы при `canMove` равном `false` (флажок снят) точка переставала двигаться. После того как вы снова установите флажок (и установите `canMove` в `true`), точка снова будет следовать за движением. Другими словами, возможность перемещения точки должна оставаться синхронизированной с тем, отмечен ли флажок. +Также есть флажок. Установка флажка переключает переменную состояния `canMove`, но эта переменная состояния нигде в коде не используется. Ваша задача — изменить код так, чтобы при `canMove` равном `false` (флажок снят) точка переставала двигаться. После того как вы снова установите флажок (и установите `canMove` в `true`), точка должна снова следовать за движением. Другими словами, возможность движения точки должна оставаться синхронизированной с состоянием флажка. @@ -1118,13 +1119,13 @@ body { #### Исследовать ошибку устаревшего значения {/*investigate-a-stale-value-bug*/} -В этом примере розовая точка должна двигаться, когда флажок установлен, и переставать двигаться, когда флажок снят. Логика для этого уже реализована: обработчик события `handleMove` проверяет переменную состояния `canMove`. +В этом примере розовая точка должна двигаться, когда флажок установлен, и останавливаться, когда флажок снят. Логика для этого уже реализована: обработчик события `handleMove` проверяет переменную состояния `canMove`. -Однако по какой-то причине переменная состояния `canMove` внутри `handleMove` кажется "устаревшей": она всегда равна `true`, даже после снятия флажка. Как это возможно? Найдите ошибку в коде и исправьте ее. +Однако по какой-то причине переменная состояния `canMove` внутри `handleMove` оказывается «устаревшей»: она всегда равна `true`, даже после снятия флажка. Как это возможно? Найдите ошибку в коде и исправьте ее. -Если вы видите подавление правила линтера, удалите его! Обычно ошибки кроются именно там. +Если вы видите подавленное правило линтера, удалите подавление! Обычно ошибки кроются именно там. @@ -1186,11 +1187,11 @@ body { -Проблема исходного кода заключалась в подавлении линтера зависимостей. Если удалить подавление, вы увидите, что этот эффект зависит от функции `handleMove`. Это имеет смысл: `handleMove` объявлена внутри тела компонента, что делает ее реактивным значением. Каждое реактивное значение должно быть указано как зависимость, иначе оно потенциально может устареть со временем! +Проблема исходного кода заключалась в подавлении линтера зависимостей. Если удалить подавление, вы увидите, что этот эффект зависит от функции `handleMove`. Это имеет смысл: `handleMove` объявлена внутри тела компонента, что делает ее реактивным значением. Каждое реактивное значение должно быть указано в качестве зависимости, иначе оно может устареть со временем! -Автор исходного кода "солгал" React, сказав, что эффект не зависит (`[]`) от каких-либо реактивных значений. Поэтому React не повторно синхронизировал эффект после изменения `canMove` (и вместе с ним `handleMove`). Поскольку React не повторно синхронизировал эффект, `handleMove`, прикрепленный как слушатель, является функцией `handleMove`, созданной во время первоначального рендеринга. Во время первоначального рендеринга `canMove` было `true`, поэтому `handleMove` из первоначального рендеринга навсегда увидит это значение. +Автор исходного кода «солгал» React, сказав, что эффект не зависит (`[]`) от каких-либо реактивных значений. Вот почему React не повторно синхронизировал эффект после изменения `canMove` (и вместе с ним `handleMove`). Поскольку React не повторно синхронизировал эффект, `handleMove`, прикрепленный как слушатель, является функцией `handleMove`, созданной во время начального рендеринга. Во время начального рендеринга `canMove` было `true`, поэтому `handleMove` из начального рендеринга навсегда увидит это значение. -**Если вы никогда не подавляете линтер, вы никогда не столкнетесь с проблемами устаревших значений.** Существует несколько способов решить эту ошибку, но вы всегда должны начинать с удаления подавления линтера. Затем измените код, чтобы исправить ошибку линтера. +**Если вы никогда не подавляете линтер, вы никогда не столкнетесь с проблемами устаревших значений.** Существует несколько способов решения этой ошибки, но вы всегда должны начинать с удаления подавления линтера. Затем измените код, чтобы исправить ошибку линтера. Вы можете изменить зависимости эффекта на `[handleMove]`, но поскольку это будет новая функция для каждого рендеринга, вы можете вообще убрать массив зависимостей. Тогда эффект *будет* повторно синхронизироваться после каждого рендеринга: @@ -1249,9 +1250,9 @@ body { -Это решение работает, но оно не идеально. Если вы добавите `console.log('Resubscribing')` внутрь эффекта, вы заметите, что он повторно подписывается после каждого повторного рендеринга. Повторная подписка происходит быстро, но все же было бы неплохо избежать этого так часто. +Это решение работает, но оно не идеально. Если вы добавите `console.log('Resubscribing')` внутрь эффекта, вы заметите, что он повторно подписывается после каждого рендеринга. Повторная подписка — это быстро, но все же было бы неплохо избегать этого так часто. -Лучшим решением будет вынести функцию `handleMove` *внутрь* эффекта. Тогда `handleMove` не будет реактивным значением, и поэтому ваш эффект не будет зависеть от функции. Вместо этого ему потребуется зависеть от `canMove`, который ваш код теперь читает изнутри эффекта. Это соответствует желаемому поведению, поскольку ваш эффект теперь будет оставаться синхронизированным со значением `canMove`: +Лучшим решением будет вынести функцию `handleMove` *внутрь* эффекта. Тогда `handleMove` не будет реактивным значением, и поэтому ваш эффект не будет зависеть от функции. Вместо этого ему нужно будет зависеть от `canMove`, которое ваш код теперь читает изнутри эффекта. Это соответствует желаемому поведению, поскольку ваш эффект теперь будет оставаться синхронизированным со значением `canMove`: @@ -1310,15 +1311,15 @@ body { Попробуйте добавить `console.log('Resubscribing')` внутрь тела эффекта и заметьте, что теперь он повторно подписывается только при переключении флажка (`canMove` изменяется) или при редактировании кода. Это лучше, чем предыдущий подход, который всегда повторно подписывался. -Вы узнаете более общий подход к этому типу проблем в разделе [Separating Events from Effects.](/learn/separating-events-from-effects) +Более общий подход к этому типу проблем вы найдете в разделе [Separating Events from Effects.](/learn/separating-events-from-effects) #### Исправьте переключение соединения {/*fix-a-connection-switch*/} -В этом примере сервис чата в `chat.js` предоставляет два разных API: `createEncryptedConnection` и `createUnencryptedConnection`. Корневой компонент `App` позволяет пользователю выбрать, использовать ли шифрование, а затем передает соответствующий метод API дочернему компоненту `ChatRoom` в качестве пропса `createConnection`. +В этом примере сервис чата в `chat.js` предоставляет два разных API: `createEncryptedConnection` и `createUnencryptedConnection`. Корневой компонент `App` позволяет пользователю выбрать, использовать ли шифрование, а затем передает соответствующий метод API дочернему компоненту `ChatRoom` как пропс `createConnection`. -Обратите внимание, что изначально в консоли отображаются сообщения о том, что соединение не зашифровано. Попробуйте установить флажок: ничего не произойдет. Однако, если вы после этого смените выбранную комнату, чат переподключится *и* включит шифрование (как вы увидите из сообщений в консоли). Это ошибка. Исправьте ошибку так, чтобы установка флажка *также* приводила к переподключению чата. +Обратите внимание, что изначально в консоли отображаются сообщения о том, что соединение не зашифровано. Попробуйте включить флажок: ничего не произойдет. Однако, если после этого вы смените выбранную комнату, чат переподключится *и* включит шифрование (как вы увидите из сообщений в консоли). Это ошибка. Исправьте ошибку так, чтобы переключение флажка *также* вызывало переподключение чата. @@ -1524,10 +1525,6 @@ It is correct that `createConnection` is a dependency. However, this code is a b ```js src/App.js import { useState } from 'react'; import ChatRoom from './ChatRoom.js'; -import { - createEncryptedConnection, - createUnencryptedConnection, -} from './chat.js'; export default function App() { const [roomId, setRoomId] = useState('general'); @@ -1556,10 +1553,7 @@ export default function App() {
); @@ -1568,110 +1562,20 @@ export default function App() { ```js src/ChatRoom.js active import { useState, useEffect } from 'react'; - -export default function ChatRoom({ roomId, createConnection }) { - useEffect(() => { - const connection = createConnection(roomId); - connection.connect(); - return () => connection.disconnect(); - }, [roomId, createConnection]); - - return

Welcome to the {roomId} room!

; -} -``` - -```js src/chat.js -export function createEncryptedConnection(roomId) { - // A real implementation would actually connect to the server - return { - connect() { - console.log('✅ 🔐 Connecting to "' + roomId + '... (encrypted)'); - }, - disconnect() { - console.log('❌ 🔐 Disconnected from "' + roomId + '" room (encrypted)'); - } - }; -} - -export function createUnencryptedConnection(roomId) { - // A real implementation would actually connect to the server - return { - connect() { - console.log('✅ Connecting to "' + roomId + '... (unencrypted)'); - }, - disconnect() { - console.log('❌ Disconnected from "' + roomId + '" room (unencrypted)'); - } - }; -} -``` - -```css -label { display: block; margin-bottom: 10px; } -``` - -
- - - -It is correct that `createConnection` is a dependency. However, this code is a bit fragile because someone could edit the `App` component to pass an inline function as the value of this prop. In that case, its value would be different every time the `App` component re-renders, so the Effect might re-synchronize too often. To avoid this, you can pass `isEncrypted` down instead: - - - -```js src/App.js -import { useState } from 'react'; -import ChatRoom from './ChatRoom.js'; import { createEncryptedConnection, createUnencryptedConnection, } from './chat.js'; -export default function App() { - const [roomId, setRoomId] = useState('general'); - const [isEncrypted, setIsEncrypted] = useState(false); - return ( - <> - - -
- - - ); -} -``` - -```js src/ChatRoom.js active -import { useState, useEffect } from 'react'; - -export default function ChatRoom({ roomId, createConnection }) { +export default function ChatRoom({ roomId, isEncrypted }) { useEffect(() => { + const createConnection = isEncrypted ? + createEncryptedConnection : + createUnencryptedConnection; const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); - }, [roomId, createConnection]); + }, [roomId, isEncrypted]); return

Welcome to the {roomId} room!

; } @@ -1715,9 +1619,9 @@ In this version, the `App` component passes a boolean prop instead of a function #### Заполните цепочку выпадающих списков {/*populate-a-chain-of-select-boxes*/} -В этом примере есть два выпадающих списка. Один позволяет пользователю выбрать планету. Другой позволяет пользователю выбрать место *на этой планете*. Второй список пока не работает. Ваша задача — сделать так, чтобы он отображал места на выбранной планете. +В этом примере есть два выпадающих списка. Один позволяет пользователю выбрать планету. Другой позволяет пользователю выбрать место *на этой планете*. Второй список пока не работает. Ваша задача — добавить дополнительный код, чтобы переменная состояния `placeList` заполнялась результатом вызова API `"/planets/" + planetId + "/places"`. -Посмотрите, как работает первый выпадающий список. Он заполняет состояние `planetList` результатом вызова API `"/planets"`. Идентификатор выбранной планеты хранится в переменной состояния `planetId`. Вам нужно найти, куда добавить дополнительный код, чтобы переменная состояния `placeList` была заполнена результатом вызова API `"/planets/" + planetId + "/places"`. +Посмотрите, как работает первый выпадающий список. Он заполняет состояние `planetList` результатом вызова API `"/planets"`. Идентификатор выбранной планеты хранится в переменной состояния `planetId`. Вам нужно найти, куда добавить дополнительный код, чтобы переменная состояния `placeList` заполнялась результатом вызова API `"/planets/" + planetId + "/places"`. Если вы реализуете это правильно, выбор планеты должен заполнить список мест. Изменение планеты должно изменить список мест.