diff --git a/content/docs/hooks-custom.md b/content/docs/hooks-custom.md
index 486eb93b2..28fdbab41 100644
--- a/content/docs/hooks-custom.md
+++ b/content/docs/hooks-custom.md
@@ -18,11 +18,11 @@ import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
- function handleStatusChange(status) {
- setIsOnline(status.isOnline);
- }
-
useEffect(() => {
+ function handleStatusChange(status) {
+ setIsOnline(status.isOnline);
+ }
+
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
@@ -44,11 +44,11 @@ import React, { useState, useEffect } from 'react';
function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);
- function handleStatusChange(status) {
- setIsOnline(status.isOnline);
- }
-
useEffect(() => {
+ function handleStatusChange(status) {
+ setIsOnline(status.isOnline);
+ }
+
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
@@ -79,11 +79,11 @@ import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
- function handleStatusChange(status) {
- setIsOnline(status.isOnline);
- }
-
useEffect(() => {
+ function handleStatusChange(status) {
+ setIsOnline(status.isOnline);
+ }
+
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
diff --git a/content/docs/hooks-effect.md b/content/docs/hooks-effect.md
index 3219fff0c..9b9fb1500 100644
--- a/content/docs/hooks-effect.md
+++ b/content/docs/hooks-effect.md
@@ -198,17 +198,17 @@ class FriendStatus extends React.Component {
Вы должно быть подумали, что нам потребуется отдельный эффект для выполнения сброса. Так как код для оформления и отмены подписки тесно связан с `useEffect`, мы решили объединить их. Если ваш эффект возвращает функцию, React выполнит её только тогда, когда наступит время сбросить эффект.
-```js{10-16}
+```js{6-16}
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
- function handleStatusChange(status) {
- setIsOnline(status.isOnline);
- }
-
useEffect(() => {
+ function handleStatusChange(status) {
+ setIsOnline(status.isOnline);
+ }
+
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Указываем, как сбросить этот эффект:
return function cleanup() {
@@ -237,6 +237,10 @@ function FriendStatus(props) {
```js
useEffect(() => {
+ function handleStatusChange(status) {
+ setIsOnline(status.isOnline);
+ }
+
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
@@ -316,15 +320,15 @@ function FriendStatusWithCounter(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
+ function handleStatusChange(status) {
+ setIsOnline(status.isOnline);
+ }
+
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
-
- function handleStatusChange(status) {
- setIsOnline(status.isOnline);
- }
// ...
}
```
@@ -394,6 +398,7 @@ function FriendStatusWithCounter(props) {
function FriendStatus(props) {
// ...
useEffect(() => {
+ // ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
@@ -449,8 +454,12 @@ useEffect(() => {
Это также работает для эффектов с этапом сброса:
-```js{6}
+```js{10}
useEffect(() => {
+ function handleStatusChange(status) {
+ setIsOnline(status.isOnline);
+ }
+
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
@@ -462,9 +471,14 @@ useEffect(() => {
>Примечание
>
->Если вы хотите использовать эту оптимизацию, обратите внимание на то, чтобы массив включал в себя **все значения из внешней области видимости, которые могут изменяться с течением времени, и которые будут использоваться эффектом**. В противном случае, ваш код будет ссылаться на устаревшее значение из предыдущих рендеров. Мы рассмотрим другие возможные оптимизации на странице [справочник API хуков](/docs/hooks-reference.html).
+>Если вы хотите использовать эту оптимизацию, обратите внимание на то, чтобы массив включал в себя **все значения из области видимости компонента (такие как пропсы и состояние), которые могут изменяться с течением времени, и которые будут использоваться эффектом**. В противном случае, ваш код будет ссылаться на устаревшее значение из предыдущих рендеров. Отдельные страницы документации рассказывают о том, [как поступить с функциями](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) и [что делать с часто изменяющимися массивами](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often).
+>
+>Если вы хотите запустить эффект и сбросить его только один раз (при монтировании и размонтировании), вы можете передать пустой массив (`[]`) вторым аргументом. React посчитает, что ваш эффект не зависит от *каких-либо* значений из пропсов или состояния и поэтому не будет выполнять повторных рендеров. Это не обрабатывается как особый случай -- он напрямую следует из логики работы входных массивов.
>
->Если вы хотите выполнять эффект и сбрасывать его однократно (при монтировании и размонтировании), вы можете передать пустой массив вторым аргументом. React посчитает, что ваш эффект не зависит от *каких-либо* значений из пропсов или состояния, и таким образом, он будет знать, что ему не надо его выполнять повторно. Это не расценивается как особый случай -- он напрямую следует из логики работы массивов при вводе. Хотя передача `[]` ближе по мысли к модели знакомых `componentDidMount` и `componentWillUnmount`, мы не советуем привыкать к этому, так как это часто приводит к багам, [как было рассмотрено ранее](#explanation-why-effects-run-on-each-update). Не забывайте, что React откладывает выполнение `useEffect` пока браузер не отрисует все изменения, поэтому выполнение дополнительной работы не является существенной проблемой.
+>Если вы передадите пустой массив (`[]`), пропсы и состояние внутри эффекта всегда будут иметь значения, присвоенные им изначально. Хотя передача `[]` ближе по модели мышления к знакомым `componentDidMount` и `componentWillUnmount`, обычно есть [более хорошие](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) [способы](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often) избежать частых повторных рендеров. Не забывайте, что React откладывает выполнение `useEffect`, пока браузер не отрисует все изменения, поэтому выполнение дополнительной работы не является существенной проблемой.
+>
+>Мы рекомендуем использовать правило [`exhaustive-deps`](https://github.com/facebook/react/issues/14920), входящее в наш пакет правил линтера [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation). Оно предупреждает, когда зависимости указаны неправильно и предлагает исправление.
+
## Следующие шаги {#next-steps}
diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md
index 05b6cddac..fb3351433 100644
--- a/content/docs/hooks-faq.md
+++ b/content/docs/hooks-faq.md
@@ -18,39 +18,43 @@ prev: hooks-reference.html
).join('\n')
-->
-* [Внедрение хуков {#adoption-strategy}](#Внедрение-хуков-adoption-strategy)
- * [В какой версии React появились хуки? {#which-versions-of-react-include-hooks}](#В-какой-версии-react-появились-хуки-which-versions-of-react-include-hooks)
- * [Надо ли переписать все мои классовые компоненты? {#do-i-need-to-rewrite-all-my-class-components}](#Надо-ли-переписать-все-мои-классовые-компоненты-do-i-need-to-rewrite-all-my-class-components)
- * [Что можно сделать с помощью хуков, чего невозможно добиться, используя классы? {#what-can-i-do-with-hooks-that-i-couldnt-with-classes}](#Что-можно-сделать-с-помощью-хуков-чего-невозможно-добиться-используя-классы-what-can-i-do-with-hooks-that-i-couldnt-with-classes)
- * [Какая часть моих знаний о React всё ещё актуальна? {#how-much-of-my-react-knowledge-stays-relevant}](#Какая-часть-моих-знаний-о-react-всё-ещё-актуальна-how-much-of-my-react-knowledge-stays-relevant)
- * [Что мне использовать: хуки, классы или оба подхода? {#should-i-use-hooks-classes-or-a-mix-of-both}](#Что-мне-использовать-хуки-классы-или-оба-подхода-should-i-use-hooks-classes-or-a-mix-of-both)
- * [Дают ли хуки все возможности классов? {#do-hooks-cover-all-use-cases-for-classes}](#Дают-ли-хуки-все-возможности-классов-do-hooks-cover-all-use-cases-for-classes)
- * [Являются ли хуки заменой рендер-пропсам и компонентам высшего порядка? {#do-hooks-replace-render-props-and-higher-order-components}](#Являются-ли-хуки-заменой-рендер-пропсам-и-компонентам-высшего-порядка-do-hooks-replace-render-props-and-higher-order-components)
- * [Как хуки повлияют на популярные API, такие как Redux `connect()` и React Router? {#what-do-hooks-mean-for-popular-apis-like-redux-connect-and-react-router}](#Как-хуки-повлияют-на-популярные-api-такие-как-redux-connect-и-react-router-what-do-hooks-mean-for-popular-apis-like-redux-connect-and-react-router)
- * [Поддерживают ли хуки статическую типизацию? {#do-hooks-work-with-static-typing}](#Поддерживают-ли-хуки-статическую-типизацию-do-hooks-work-with-static-typing)
- * [Как тестировать компоненты, которые используют хуки? {#how-to-test-components-that-use-hooks}](#Как-тестировать-компоненты-которые-используют-хуки-how-to-test-components-that-use-hooks)
- * [Что конкретно проверяют правила линтера в хуках?{#what-exactly-do-the-lint-rules-enforce}](#Что-конкретно-проверяют-правила-линтера-в-хукахwhat-exactly-do-the-lint-rules-enforce)
-* [От классов к хукам {#from-classes-to-hooks}](#От-классов-к-хукам-from-classes-to-hooks)
- * [Как методы жизненного цикла соответствуют хукам? {#how-do-lifecycle-methods-correspond-to-hooks}](#Как-методы-жизненного-цикла-соответствуют-хукам-how-do-lifecycle-methods-correspond-to-hooks)
- * [Существует что-нибудь наподобие полей экземпляра? {#is-there-something-like-instance-variables}](#Существует-что-нибудь-наподобие-полей-экземпляра-is-there-something-like-instance-variables)
- * [Сколько переменных состояния я могу использовать – одну или несколько? {#should-i-use-one-or-many-state-variables}](#Сколько-переменных-состояния-я-могу-использовать--одну-или-несколько-should-i-use-one-or-many-state-variables)
- * [Могу ли я использовать эффект только на обновлениях компонента? {#can-i-run-an-effect-only-on-updates}](#Могу-ли-я-использовать-эффект-только-на-обновлениях-компонента-can-i-run-an-effect-only-on-updates)
- * [Как получить предыдущие пропсы или состояние? {#how-to-get-the-previous-props-or-state}](#Как-получить-предыдущие-пропсы-или-состояние-how-to-get-the-previous-props-or-state)
- * [Как я могу реализовать `getDerivedStateFromProps`? {#how-do-i-implement-getderivedstatefromprops}](#Как-я-могу-реализовать-getderivedstatefromprops-how-do-i-implement-getderivedstatefromprops)
- * [Существует что-нибудь наподобие forceUpdate? {#is-there-something-like-forceupdate}](#Существует-что-нибудь-наподобие-forceupdate-is-there-something-like-forceupdate)
- * [Могу ли я изменить реф, переданный в функциональный компонент? {#can-i-make-a-ref-to-a-function-component}](#Могу-ли-я-изменить-реф-переданный-в-функциональный-компонент-can-i-make-a-ref-to-a-function-component)
- * [Что значит `const [thing, setThing] = useState()`? {#what-does-const-thing-setthing--usestate-mean}](#Что-значит-const-thing-setthing--usestate-what-does-const-thing-setthing--usestate-mean)
-* [Оптимизации производительности {#performance-optimizations}](#Оптимизации-производительности-performance-optimizations)
- * [Могу ли я пропустить эффект при обновлениях? {#can-i-skip-an-effect-on-updates}](#Могу-ли-я-пропустить-эффект-при-обновлениях-can-i-skip-an-effect-on-updates)
- * [Как я могу реализовать `shouldComponentUpdate`? {#how-do-i-implement-shouldcomponentupdate}](#Как-я-могу-реализовать-shouldcomponentupdate-how-do-i-implement-shouldcomponentupdate)
- * [Как закешировать вычисления? {#how-to-memoize-calculations}](#Как-закешировать-вычисления-how-to-memoize-calculations)
- * [Как лениво создавать большие объекты? {#how-to-create-expensive-objects-lazily}](#Как-лениво-создавать-большие-объекты-how-to-create-expensive-objects-lazily)
- * [Являются ли хуки медленными из-за создания функций на каждом рендере? {#are-hooks-slow-because-of-creating-functions-in-render}](#Являются-ли-хуки-медленными-из-за-создания-функций-на-каждом-рендере-are-hooks-slow-because-of-creating-functions-in-render)
- * [Как избежать передачи колбэков вниз? {#how-to-avoid-passing-callbacks-down}](#Как-избежать-передачи-колбэков-вниз-how-to-avoid-passing-callbacks-down)
- * [Как получить часто изменяемое значение из хука `useCallback`? {#how-to-read-an-often-changing-value-from-usecallback}](#Как-получить-часто-изменяемое-значение-из-хука-usecallback-how-to-read-an-often-changing-value-from-usecallback)
-* [Под капотом {#under-the-hood}](#Под-капотом-under-the-hood)
- * [Как React связывает вызовы хуков с компонентом? {#how-does-react-associate-hook-calls-with-components}](#Как-react-связывает-вызовы-хуков-с-компонентом-how-does-react-associate-hook-calls-with-components)
- * [Что послужило прообразом хуков? {#what-is-the-prior-art-for-hooks}](#Что-послужило-прообразом-хуков-what-is-the-prior-art-for-hooks)
+* [Внедрение хуков](#adoption-strategy)
+ * [В какой версии React появились хуки?](#which-versions-of-react-include-hooks)
+ * [Нужно ли переписать все мои классовые компоненты?](#do-i-need-to-rewrite-all-my-class-components)
+ * [Что можно сделать с помощью хуков, чего невозможно добиться, используя классы?](#what-can-i-do-with-hooks-that-i-couldnt-with-classes)
+ * [Какая часть моих знаний о React всё ещё актуальна?](#how-much-of-my-react-knowledge-stays-relevant)
+ * [Что мне использовать: хуки, классы или оба подхода?](#should-i-use-hooks-classes-or-a-mix-of-both)
+ * [Дают ли хуки все возможности классов?](#do-hooks-cover-all-use-cases-for-classes)
+ * [Являются ли хуки заменой рендер-пропсам и компонентам высшего порядка?](#do-hooks-replace-render-props-and-higher-order-components)
+ * [Как хуки повлияют на популярные API, такие как Redux `connect()` и React Router?](#what-do-hooks-mean-for-popular-apis-like-redux-connect-and-react-router)
+ * [Поддерживают ли хуки статическую типизацию?](#do-hooks-work-with-static-typing)
+ * [Как тестировать компоненты, которые используют хуки?](#how-to-test-components-that-use-hooks)
+ * [Что конкретно проверяют правила линтера в хуках?](#what-exactly-do-the-lint-rules-enforce)
+* [От классов к хукам](#from-classes-to-hooks)
+ * [Как методы жизненного цикла соответствуют хукам?](#how-do-lifecycle-methods-correspond-to-hooks)
+ * [Как осуществлять запросы данных с помощью хуков?](#how-can-i-do-data-fetching-with-hooks)
+ * [Существует что-нибудь наподобие полей экземпляра?](#is-there-something-like-instance-variables)
+ * [Сколько переменных состояния я могу использовать – одну или несколько?](#should-i-use-one-or-many-state-variables)
+ * [Могу ли я использовать эффект только на обновлениях компонента?](#can-i-run-an-effect-only-on-updates)
+ * [Как получить предыдущие пропсы или состояние?](#how-to-get-the-previous-props-or-state)
+ * [Почему я вижу устаревшие пропсы или состояние в моей функции?](#why-am-i-seeing-stale-props-or-state-inside-my-function)
+ * [Как я могу реализовать `getDerivedStateFromProps`?](#how-do-i-implement-getderivedstatefromprops)
+ * [Существует что-нибудь наподобие forceUpdate?](#is-there-something-like-forceupdate)
+ * [Могу ли я изменить реф, переданный в функциональный компонент?](#can-i-make-a-ref-to-a-function-component)
+ * [Что значит `const [thing, setThing] = useState()`?](#what-does-const-thing-setthing--usestate-mean)
+* [Оптимизации производительности](#performance-optimizations)
+ * [Могу ли я пропустить эффект при обновлениях?](#can-i-skip-an-effect-on-updates)
+ * [Безопасно ли не указывать функции в списке зависимостей?](#is-it-safe-to-omit-functions-from-the-list-of-dependencies)
+ * [Что делать, если зависимости эффекта изменяются слишком часто?](#what-can-i-do-if-my-effect-dependencies-change-too-often)
+ * [Как я могу реализовать `shouldComponentUpdate`?](#how-do-i-implement-shouldcomponentupdate)
+ * [Как закешировать вычисления?](#how-to-memoize-calculations)
+ * [Как лениво создавать большие объекты?](#how-to-create-expensive-objects-lazily)
+ * [Являются ли хуки медленными из-за создания функций на каждом рендере?](#are-hooks-slow-because-of-creating-functions-in-render)
+ * [Как избежать передачи колбэков вниз?](#how-to-avoid-passing-callbacks-down)
+ * [Как получить часто изменяемое значение из хука `useCallback`?](#how-to-read-an-often-changing-value-from-usecallback)
+* [Под капотом](#under-the-hood)
+ * [Как React связывает вызовы хуков с компонентом?](#how-does-react-associate-hook-calls-with-components)
+ * [Что послужило прообразом хуков?](#what-is-the-prior-art-for-hooks)
## Внедрение хуков {#adoption-strategy}
@@ -205,6 +209,10 @@ it('can render and update a counter', () => {
* `componentDidCatch` и `getDerivedStateFromError`: В данный момент не существует хуков-аналогов для этих методов, но они будут скоро добавлены.
+### Как осуществлять запросы данных с помощью хуков? {#how-can-i-do-data-fetching-with-hooks}
+
+Ознакомьтесь [со статьёй](https://www.robinwieruch.de/react-hooks-fetch-data/), которая рассказывает как делать запросы данных с помощью хуков.
+
### Существует что-нибудь наподобие полей экземпляра? {#is-there-something-like-instance-variables}
Да! Хук [`useRef()`](/docs/hooks-reference.html#useref) может использоваться не только для DOM-рефов. Реф – это общий контейнер, а его поле `current` -- изменяемое и может хранить любое значение, подобно полю экземпляра класса.
@@ -364,6 +372,45 @@ function Counter() {
Также смотрите [рекомендованный паттерн для производного состояния](#how-do-i-implement-getderivedstatefromprops).
+### Почему я вижу устаревшие пропсы или состояние в моей функции? {#why-am-i-seeing-stale-props-or-state-inside-my-function}
+
+Любая функция внутри компонента, включая эффекты и обработчики событий, «видит» пропсы и состояние из функции `render()`, в которой она была создана. Рассмотрим такой код:
+
+```js
+function Example() {
+ const [count, setCount] = useState(0);
+
+ function handleAlertClick() {
+ setTimeout(() => {
+ alert('Вы кликнули по: ' + count);
+ }, 3000);
+ }
+
+ return (
+
+
Вы кликнули {count} раз(а)
+
+
+
+ );
+}
+```
+
+Если вы сперва кликните по "Показать предупреждение", а потом увеличите счётчик, то предупреждение отобразит значение переменной `count` **на момент нажатия по кнопке "Показать предупреждение"**. Такое поведение предотвращает баги в коде, который предполагает, что пропсы и состояние не меняются.
+
+Если вы намеренно хотите считать из асинхронного колбэка *свежайшее* состояние, вы можете сперва сохранить его [в реф](/docs/hooks-faq.html#is-there-something-like-instance-variables), потом изменить его и затем считать его из рефа.
+
+Наконец, возможна другая ситуация, почему вы видите устаревшие пропсы или состояние: когда вы используете оптимизацию с помощью «массива зависимостей», но неправильно указали какие-то зависимости. Например, если эффект передаёт вторым параметром `[]`, но при этом использует `someProp`, то он продолжит «видеть» исходное значение `someProp`. Правильным решением является либо исправление массива, либо отказ от его использования.
+По этим ссылкам описаны [подходы к функциям](#is-it-safe-to-omit-functions-from-the-list-of-dependencies) в аналогичных ситуациях и [другие известные способы](#what-can-i-do-if-my-effect-dependencies-change-too-often) снижения частоты вызова эффектов, исключающие передачу неправильных зависимостей.
+
+>Примечание
+>
+>Мы предоставляем правило [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) в нашем пакете линтера [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation). Оно предупреждает о неправильно указанных зависимостях и рекомендует их исправить.
+
### Как я могу реализовать `getDerivedStateFromProps`? {#how-do-i-implement-getderivedstatefromprops}
Несмотря на то, что вам скорее всего [это не нужно](/blog/2018/06/07/you-probably-dont-need-derived-state.html), в редких случаях (например, для реализации компонента ``), вы можете изменять состояние прямо во время рендера. React моментально сделает повторный рендер компонента с обновлённым состоянием сразу после первого рендера, и это не будет затратно.
@@ -419,6 +466,208 @@ function ScrollView({row}) {
Да. Ознакомьтесь с [условным вызовом эффектов](/docs/hooks-reference.html#conditionally-firing-an-effect). Обратите внимание, что забыв обработать обновления, вы зачастую можете [создать ошибки](/docs/hooks-effect.html#explanation-why-effects-run-on-each-update). Поэтому это и не является поведением по умолчанию.
+### Безопасно ли не указывать функции в списке зависимостей? {#is-it-safe-to-omit-functions-from-the-list-of-dependencies}
+
+Вообще говоря, это небезопасно.
+
+```js{3,8}
+function Example() {
+ function doSomething() {
+ console.log(someProp);
+ }
+
+ useEffect(() => {
+ doSomething();
+ }, []); // 🔴 Так делать небезопасно (вызывать `doSomething`, который использует `someProp`)
+}
+```
+
+Сложно помнить, какие пропсы и состояние используются функцией определённой вне эффекта. Именно поэтому, **лучше объявлять функции нужные эффекту *внутри* него**. Тогда легче увидеть, от каких значений из области видимости компонента зависит эффект:
+
+```js{4,8}
+function Example() {
+ useEffect(() => {
+ function doSomething() {
+ console.log(someProp);
+ }
+
+ doSomething();
+ }, [someProp]); // ✅ Правильно (наш эффект использует только `someProp`)
+}
+```
+
+Если после такого изменения мы видим, что никакие значения из области видимости компонента не используются, то можно безопасно указать `[]`:
+
+```js{7-8}
+useEffect(() => {
+ function doSomething() {
+ console.log('Привет');
+ }
+
+ doSomething();
+}, []); // ✅ Так можно поступить в этом примере, потому что
+ // нет зависимости от значений из области видимости компонента
+```
+
+В зависимости от ситуации, может пригодиться один из вариантов, описанных ниже.
+
+>Примечание
+>
+>Мы предоставляем правило [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) в пакете нашего линтера [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation). Оно поможет выяалять компоненты, не обрабатывающие обновления должным образом.
+
+Давайте разберёмся, почему это важно.
+
+Когда вы указываете [список зависимостей](/docs/hooks-reference.html#conditionally-firing-an-effect) через последний аргумент хуков `useEffect`, `useMemo`, `useCallback` или `useImperativeHandle`, в него должны войти все использованные значения, которые задействованы в потоке данных React, включая пропсы, состояние и их производные.
+
+Безопасно опускать в списке **только** те функции, которые не используют ни сами, ни через функции, которые они вызывают, пропсы, состояние или их производные. Так, в следующем примере показан баг:
+
+```js{5,12}
+function ProductPage({ productId }) {
+ const [product, setProduct] = useState(null);
+
+ async function fetchProduct() {
+ const response = await fetch('http://myapi/product' + productId); // Использует проп productId
+ const json = await response.json();
+ setProduct(json);
+ }
+
+ useEffect(() => {
+ fetchProduct();
+ }, []); // 🔴 Неправильно, потому что `fetchProduct` использует `productId`
+ // ...
+}
+```
+
+**Рекомендовано исправлять такой код, перемещая функцию _внутрь_ вашего эффекта**. Это помогает замечать, какие пропсы и состояние используются вашим эффектом, и убедиться, что они перечислены в списке зависимостей:
+
+```js{5-10,13}
+function ProductPage({ productId }) {
+ const [product, setProduct] = useState(null);
+
+ useEffect(() => {
+ // Переместив эту функцию внутрь эффекта, мы ясно видим, какие значения она использует.
+ async function fetchProduct() {
+ const response = await fetch('http://myapi/product' + productId);
+ const json = await response.json();
+ setProduct(json);
+ }
+
+ fetchProduct();
+ }, [productId]); // ✅ Правильно, потому что эффект использует только productId
+ // ...
+}
+```
+
+Это также позволяет обрабатывать ответы, пришедшие не в порядке запросов, с помощью локальной переменной внутри эффекта:
+
+```js{2,6,8}
+ useEffect(() => {
+ let ignore = false;
+ async function fetchProduct() {
+ const response = await fetch('http://myapi/product/' + productId);
+ const json = await response.json();
+ if (!ignore) setProduct(json);
+ }
+ return () => { ignore = true };
+ }, [productId]);
+```
+
+Мы переместили функцию внутрь эффекта, так что её не нужно указывать в списке зависимостей.
+
+>Совет
+>
+>Прочитайте [эту статью](https://www.robinwieruch.de/react-hooks-fetch-data/), чтобы узнать больше о том, как запрашивать данные с помощью хуков.
+
+**Если по какой-то причине вы _не_ можете переместить функцию в эффект, есть другие варианты:**
+
+* **Можно попробовать поместить функцию снаружи компонента**. В таком случае она гарантированно не будет ссылаться на пропсы и состояние, так что её не требуется перечислять в списке зависимостей.
+* Если функция, которую вы вызываете, является чистым вычислением и её безопасно вызывать во время рендера, вы можете **вызвать её снаружи эффекта, а не внутри**, и сделать эффект зависящим от результата этого вызова, а не от самой функции.
+* В крайнем случае можете **добавить саму функцию в список зависимостей эффекта, но _обернув её определение_** в хук [`useCallback`](/docs/hooks-reference.html#usecallback). Тогда функция будет оставаться неизменной, до тех пор, пока не изменятся её зависимости:
+
+```js{2-5,13}
+function ProductPage({ productId }) {
+ // ✅ Оборачиваем в useCallback, чтобы избежать изменений при каждом рендере
+ const fetchProduct = useCallback(() => {
+ // ... Что-то делаем с productId ...
+ }, [productId]); // ✅ Перечисляем все зависимости useCallback
+
+ return ;
+}
+
+function ProductDetails({ fetchProduct })
+ useEffect(() => {
+ fetchProduct();
+ }, [fetchProduct]); // ✅ Перечисляем все зависимости useEffect
+ // ...
+}
+```
+
+Заметьте, что в примере выше мы всё ещё **должны** указать функцию в списке зависимостей. Тогда гарантируется, что изменение пропа `productId` у `ProductPage` приведёт к повторному запрашиванию данных в компоненте `ProductDetails`.
+
+### Что делать, если зависимости эффекта изменяются слишком часто? {#what-can-i-do-if-my-effect-dependencies-change-too-often}
+
+Бывают случаи, когда эффект может зависеть от состояние, которое очень часто изменяется. У вас может возникнуть желание не включать это состояние в список зависимостей хука, но это как правило приводит к багам:
+
+```js{6,9}
+function Counter() {
+ const [count, setCount] = useState(0);
+
+ useEffect(() => {
+ const id = setInterval(() => {
+ setCount(count + 1); // Этот эффект зависит от переменной состояния `count`
+ }, 1000);
+ return () => clearInterval(id);
+ }, []); // 🔴 Баг: `count` не указан в качестве зависимости
+
+ return
{count}
;
+}
+```
+
+Если переписать список зависимостей как `[count]`, то баг будет устранён, но это приведёт к сбрасыванию интервала при каждом изменении. Такое поведение может быть нежелательно. Чтобы исправить это, мы можем применить [форму функционального обновления хука `setState`](/docs/hooks-reference.html#functional-updates), которая позволяет указать, *как* должно меняться состояние, не ссылаясь явно на *текущее* состояние:
+
+```js{6,9}
+function Counter() {
+ const [count, setCount] = useState(0);
+
+ useEffect(() => {
+ const id = setInterval(() => {
+ setCount(c => c + 1); // ✅ Эта строчка не зависит от внешней переменной `count`
+ }, 1000);
+ return () => clearInterval(id);
+ }, []); // ✅ Наш эффект не использует никаких переменных из области видимости компонента
+
+ return
{count}
;
+}
+```
+
+(Идентичность функции `setCount` гарантирована, поэтому её можно безопасно не включать в список зависимостей.)
+
+В более сложных случаях (например, когда одно состояние зависит от другого), попробуйте перенести логику обновления состояния из хука эффекта в хук [`useReducer`](/docs/hooks-reference.html#usereducer). [Эта статья](https://adamrackis.dev/state-and-use-reducer/) иллюстрирует пример того, как это сделать. **Идентичность функции `dispatch` из хука `useReducer` всегда стабильна** — даже если функция редюсера объявлена внутри компонента и считывает его пропсы.
+
+В крайнем случае если вы хотите реализовать что-то наподобие `this` в классах, вы можете [использовать реф](/docs/hooks-faq.html#is-there-something-like-instance-variables), чтобы хранить в нём изменяемую переменную. Тогда можно писать и читать из него. Например:
+
+```js{2-6,10-11,16}
+function Example(props) {
+ // Сохраняем свежайшие значения пропсов в ref
+ let latestProps = useRef(props);
+ useEffect(() => {
+ latestProps.current = props;
+ });
+
+ useEffect(() => {
+ function tick() {
+ // Считываем свежайшие значения пропсов в любое время
+ console.log(latestProps.current);
+ }
+
+ const id = setInterval(tick, 1000);
+ return () => clearInterval(id);
+ }, []); // Этот эффект никогда не исполняется повторно
+}
+```
+
+Рекомендуется прибегать к этому подходу только если не удалось найти лучшей альтернативы, поскольку компоненты, полагающиеся на изменчивость, менее предсказуемы. Если какой-то конкретный паттерн плохо получается выразить, [откройте ишью на GitHub](https://github.com/facebook/react/issues/new) с примером исполняемого кода и мы постараемся помочь.
+
### Как я могу реализовать `shouldComponentUpdate`? {#how-do-i-implement-shouldcomponentupdate}
Вы можете обернуть функциональный компонент в вызов `React.memo` для поверхностного сравнения его пропсов:
diff --git a/content/docs/hooks-reference.md b/content/docs/hooks-reference.md
index 01974bc63..363715946 100644
--- a/content/docs/hooks-reference.md
+++ b/content/docs/hooks-reference.md
@@ -45,6 +45,10 @@ setState(newState);
Во время последующих повторных рендеров первое значение, возвращаемое `useState`, всегда будет самым последним состоянием после применения обновлений.
+>Примечание
+>
+>React гарантирует, что идентичность функции `setState` стабильна и не изменяется при повторных рендерах. Поэтому её можно безопасно не включать в списки зависимостей хуков `useEffect` и `useCallback`.
+
#### Функциональные обновления {#functional-updates}
Если новое состояние вычисляется с использованием предыдущего состояния, вы можете передать функцию `setState`. Функция получит предыдущее значение и вернёт обновленное значение. Вот пример компонента счётчик, который использует обе формы `setState`:
@@ -132,7 +136,7 @@ useEffect(() => {
#### Условное срабатывание эффекта {#conditionally-firing-an-effect}
-По умолчанию эффекты запускаются после каждого завершённого рендера. Таким образом, эффект всегда пересоздаётся, если одно из его входных значений изменяется.
+По умолчанию эффекты запускаются после каждого завершённого рендера. Таким образом, эффект всегда пересоздаётся, если значение какой-то из зависимости изменилось.
Однако в некоторых случаях это может быть излишним, например, в примере подписки из предыдущего раздела. Нам не нужно создавать новую подписку на каждое обновление, а только если изменился проп `source`.
@@ -152,11 +156,17 @@ useEffect(
Теперь подписка будет создана повторно только при изменении `props.source`.
-Передача пустого массива `[]` входных данных говорит React, что ваш эффект не зависит от каких-либо значений компонента, так что эффект будет работать только при монтировании и очищаться при размонтировании; он не будет запускаться на обновлениях.
-
-> Примечание
+>Примечание
+>
+>Если вы хотите использовать эту оптимизацию, обратите внимание на то, чтобы массив включал в себя **все значения из области видимости компонента (такие как пропсы и состояние), которые могут изменяться с течением времени, и которые будут использоваться эффектом**. В противном случае, ваш код будет ссылаться на устаревшее значение из предыдущих рендеров. Отдельные страницы документации рассказывают о том, [как поступить с функциями](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) и [что делать с часто изменяющимися массивами](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often).
+>
+>Если вы хотите запустить эффект и сбросить его только один раз (при монтировании и размонтировании), вы можете передать пустой массив (`[]`) вторым аргументом. React посчитает, что ваш эффект не зависит от *каких-либо* значений из пропсов или состояния и поэтому не будет выполнять повторных рендеров. Это не обрабатывается как особый случай -- он напрямую следует из логики работы входных массивов.
>
-> Массив входных данных не передаётся в качестве аргументов функции эффекта. Концептуально, однако, это то, что они представляют: каждое значение, на которое ссылается функция эффекта, должно также появиться в массиве входных данных. В будущем достаточно продвинутый компилятор сможет создать этот массив автоматически.
+>Если вы передадите пустой массив (`[]`), пропсы и состояние внутри эффекта всегда будут иметь значения, присвоенные им изначально. Хотя передача `[]` ближе по модели мышления к знакомым `componentDidMount` и `componentWillUnmount`, обычно есть [более хорошие](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) [способы](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often) избежать частых повторных рендеров. Не забывайте, что React откладывает выполнение `useEffect`, пока браузер не отрисует все изменения, поэтому выполнение дополнительной работы не является существенной проблемой.
+>
+>Мы рекомендуем использовать правило [`exhaustive-deps`](https://github.com/facebook/react/issues/14920), входящее в наш пакет правил линтера [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation). Оно предупреждает, когда зависимости указаны неправильно и предлагает исправление.
+
+Массив зависимостей не передаётся в качестве аргументов функции эффекта. Концептуально, однако, это то, что они представляют: каждое значение, на которое ссылается функция эффекта, должно также появиться в массиве зависимостей. В будущем достаточно продвинутый компилятор сможет создать этот массив автоматически.
### `useContext` {#usecontext}
@@ -210,6 +220,10 @@ function Counter({initialState}) {
}
```
+>Примечание
+>
+>React гарантирует, что идентичность функции `setState` стабильна и не изменяется при повторных рендерах. Поэтому её можно безопасно не включать в списки зависимостей хуков `useEffect` и `useCallback`.
+
#### Указание начального состояния {#specifying-the-initial-state}
Существует два разных способа инициализации состояния `useReducer`. Вы можете выбрать любой из них в зависимости от ситуации. Самый простой способ -- передать начальное состояние в качестве второго аргумента:
@@ -282,13 +296,15 @@ const memoizedCallback = useCallback(
Возвращает [запомненный](https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F) колбэк.
-Передайте встроенный колбэк и массив входных данных. Хук `useCallback` вернёт запомненную версию колбэка, который изменяется только в случае изменения одного из входных данных. Это полезно при передаче колбэков оптимизированным дочерним компонентам, которые полагаются на равенство ссылок для предотвращения ненужных рендеров (например, `shouldComponentUpdate`).
+Передайте встроенный колбэк и массив зависимостей. Хук `useCallback` вернёт запомненную версию колбэка, который изменяется только в случае изменения значения одной из зависимостей. Это полезно при передаче колбэков оптимизированным дочерним компонентам, которые полагаются на равенство ссылок для предотвращения ненужных рендеров (например, `shouldComponentUpdate`).
-`useCallback(fn, inputs)` – это эквивалент `useMemo(() => fn, inputs)`.
+`useCallback(fn, deps)` – это эквивалент `useMemo(() => fn, deps)`.
> Примечание
>
-> Массив входных данных не передаётся в качестве аргументов для обратного вызова. Концептуально, однако, это то, что они представляют: каждое значение, указанное в обратном вызове, должно также появиться в массиве входных данных. В будущем достаточно продвинутый компилятор может создать этот массив автоматически.
+> Массив зависимостей не передаётся в качестве аргументов для обратного вызова. Концептуально, однако, это то, что они представляют: каждое значение, указанное в обратном вызове, должно также появиться в массиве зависимостей. В будущем достаточно продвинутый компилятор может создать этот массив автоматически.
+>
+> Мы рекомендуем использовать правило [`exhaustive-deps`](https://github.com/facebook/react/issues/14920), входящее в наш пакет правил линтера [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation). Оно предупреждает, когда зависимости указаны неправильно и предлагает исправление.
### `useMemo` {#usememo}
@@ -298,7 +314,7 @@ const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Возвращает [запомненное](https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%BC%D0%BE%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F) значение.
-Передайте «создающую» функцию и массив входных данных. `useMemo` будет повторно вычислять запомненное значение только тогда, когда одно из входных данных изменится. Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендере.
+Передайте «создающую» функцию и массив зависимостей. `useMemo` будет повторно вычислять запомненное значение только тогда, когда значение какой-либо из зависимостей изменилось. Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендере.
Помните, что функция, переданная `useMemo`, запускается во время рендеринга. Не делайте там ничего, что вы обычно не делаете во время рендеринга. Например, побочные эффекты принадлежат `useEffect`, а не `useMemo`.
@@ -308,7 +324,9 @@ const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
> Примечание
>
-> Массив входных данных не передаётся в качестве аргументов функции. Концептуально, однако, это то, что они представляют: каждое значение, на которое ссылается функция, должно также появиться в массиве входных данных. В будущем достаточно продвинутый компилятор может создать этот массив автоматически.
+> Массив зависимостей не передаётся в качестве аргументов функции. Концептуально, однако, это то, что они представляют: каждое значение, на которое ссылается функция, должно также появиться в массиве зависимостей. В будущем достаточно продвинутый компилятор может создать этот массив автоматически.
+>
+> Мы рекомендуем использовать правило [`exhaustive-deps`](https://github.com/facebook/react/issues/14920), входящее в наш пакет правил линтера [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation). Оно предупреждает, когда зависимости указаны неправильно и предлагает исправление.
### `useRef` {#useref}
@@ -341,7 +359,7 @@ function TextInputWithFocusButton() {
### `useImperativeHandle` {#useimperativehandle}
```js
-useImperativeHandle(ref, createHandle, [inputs])
+useImperativeHandle(ref, createHandle, [deps])
```
`useImperativeHandle` настраивает значение экземпляра, которое предоставляется родительским компонентам при использовании `ref`. Как всегда, в большинстве случаев следует избегать императивного кода, использующего ссылки. `useImperativeHandle` должен использоваться с `forwardRef`:
diff --git a/content/docs/hooks-rules.md b/content/docs/hooks-rules.md
index c64d6037a..30562ca71 100644
--- a/content/docs/hooks-rules.md
+++ b/content/docs/hooks-rules.md
@@ -40,7 +40,8 @@ npm install eslint-plugin-react-hooks
],
"rules": {
// ...
- "react-hooks/rules-of-hooks": "error"
+ "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
+ "react-hooks/exhaustive-deps": "warning" // Checks effect dependencies
}
}
```
diff --git a/content/languages.yml b/content/languages.yml
index ff0be2c1d..5e0958260 100644
--- a/content/languages.yml
+++ b/content/languages.yml
@@ -26,7 +26,7 @@
- name: German
translated_name: Deutsch
code: de
- status: 0
+ status: 1
- name: Greek
translated_name: Ελληνικά
code: el
@@ -50,7 +50,7 @@
- name: Hebrew
translated_name: עברית
code: he
- status: 0
+ status: 1
- name: Hindi
translated_name: हिन्दी
code: hi
@@ -78,7 +78,7 @@
- name: Korean
translated_name: 한국어
code: ko
- status: 0
+ status: 1
- name: Kurdish
translated_name: کوردی
code: ku
@@ -102,11 +102,11 @@
- name: Polish
translated_name: Polski
code: pl
- status: 0
+ status: 1
- name: Portuguese (Brazil)
translated_name: Português do Brasil
code: pt-br
- status: 1
+ status: 2
- name: Portuguese (Portugal)
translated_name: Português europeu
code: pt-pt
@@ -118,7 +118,7 @@
- name: Russian
translated_name: Русский
code: ru
- status: 1
+ status: 2
- name: Sinhala
translated_name: සිංහල
code: si
@@ -137,7 +137,7 @@
- name: Turkish
translated_name: Türkçe
code: tr
- status: 0
+ status: 1
- name: Ukrainian
translated_name: Українська
code: uk