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