v19.2Latest

Сохранение чистоты компонентов

Некоторые функции JavaScript являютсячистыми.Чистые функции выполняют только вычисления и ничего более. Строго придерживаясь написания компонентов как чистых функций, вы можете избежать целого класса запутанных ошибок и непредсказуемого поведения по мере роста кодовой базы. Однако, чтобы получить эти преимущества, необходимо соблюдать несколько правил.

Вы узнаете
  • Что такое чистота и как она помогает избегать ошибок
  • Как сохранять компоненты чистыми, не внося изменения в фазу рендеринга
  • Как использовать Строгий режим для поиска ошибок в компонентах

Чистота: компоненты как формулы

В информатике (и особенно в мире функционального программирования)чистая функция— это функция со следующими характеристиками:

  • Она занимается своим делом.Она не изменяет никакие объекты или переменные, существовавшие до её вызова.
  • Одинаковые входы — одинаковый выход.При одинаковых входных данных чистая функция всегда должна возвращать один и тот же результат.

Возможно, вы уже знакомы с одним примером чистых функций: математическими формулами.

Рассмотрим эту математическую формулу:y= 2x.

Еслиx= 2, тоy= 4. Всегда.

Еслиx= 3, тоy= 6. Всегда.

Еслиx= 3,yне будет иногда равняться9или–1или2.5в зависимости от времени суток или состояния фондового рынка.

Еслиy= 2x и x= 3, тоyбудетвсегдаравняться6.

Если мы превратим это в функцию JavaScript, она будет выглядеть так:

В приведённом выше примереdouble— эточистая функция.Если передать ей3, она вернёт6. Всегда.

React построен вокруг этой концепции.React предполагает, что каждый написанный вами компонент является чистой функцией.Это означает, что компоненты React, которые вы пишете, должны всегда возвращать один и тот же JSX при одинаковых входных данных:

Когда вы передаётеdrinkers={2} в Recipe, он вернёт JSX, содержащий2 чашки воды. Всегда.

Если вы передадитеdrinkers={4}, он вернёт JSX, содержащий4 чашки воды. Всегда.

Прямо как математическая формула.

Вы можете думать о своих компонентах как о рецептах: если следовать им и не вводить новые ингредиенты в процессе приготовления, вы получите одно и то же блюдо каждый раз. Это «блюдо» — JSX, который компонент передаёт React длярендеринга.

Рецепт чая для x человек: возьмите x чашек воды, добавьте x ложек чая и 0.5x ложек специй, и 0.5x чашек молока

Иллюстрация:Рейчел Ли Нейборс

Побочные эффекты: (не)преднамеренные последствия

Процесс рендеринга React всегда должен быть чистым. Компоненты должны тольковозвращатьсвой JSX, а неизменятькакие-либо объекты или переменные, существовавшие до рендеринга — это сделало бы их нечистыми!

Вот компонент, который нарушает это правило:

Этот компонент читает и записывает переменнуюguest, объявленную вне его. Это означает, чтомногократный вызов этого компонента будет давать разный JSX!Более того, еслидругиекомпоненты будут читатьguest, они тоже будут давать разный JSX в зависимости от момента их рендеринга! Это непредсказуемо.

Возвращаясь к нашей формулеy= 2x, теперь даже еслиx= 2, мы не можем быть уверены, чтоy= 4. Наши тесты могут падать, пользователи будут озадачены, самолёты начнут падать с неба — вы понимаете, к каким запутанным багам это может привести!

Вы можете исправить этот компонент,передав guest как проп:

Теперь ваш компонент чистый, так как возвращаемый им JSX зависит только от пропаguest.

В общем случае, вы не должны ожидать, что ваши компоненты будут рендериться в каком-то определённом порядке. Неважно, вызываете ли выy= 2xдо или послеy= 5x: обе формулы вычисляются независимо друг от друга. Точно так же каждый компонент должен «думать только за себя» и не пытаться координироваться с другими или зависеть от них во время рендеринга. Рендеринг похож на школьный экзамен: каждый компонент должен вычислять JSX самостоятельно!

Deep Dive
Обнаружение нечистых вычислений с помощью StrictMode

Локальная мутация: маленький секрет вашего компонента

В приведённом выше примере проблема заключалась в том, что компонент изменилсуществующуюпеременную во время рендеринга. Это часто называют«мутацией», чтобы звучало немного страшнее. Чистые функции не мутируют переменные вне области видимости функции или объекты, созданные до вызова — это делает их нечистыми!

Однакосовершенно нормально изменять переменные и объекты, которые вытолько чтосоздали во время рендеринга.В этом примере вы создаёте массив[], присваиваете его переменнойcups, а затемpushдобавляете в него дюжину чашек:

Если бы переменнаяcupsили массив[]были созданы вне функцииTeaGathering, это было бы огромной проблемой! Вы бы изменялисуществующийобъект, добавляя в него элементы.

Однако это нормально, потому что вы создали ихво время того же рендеринга, внутриTeaGathering. Никакой код внеTeaGatheringникогда не узнает об этом. Это называется«локальной мутацией»— это как маленький секрет вашего компонента.

Где выможетевызывать побочные эффекты

Хотя функциональное программирование сильно полагается на чистоту, в какой-то момент, где-точто-тодолжно измениться. В этом, собственно, и заключается смысл программирования! Эти изменения — обновление экрана, запуск анимации, изменение данных — называютсяпобочными эффектами.Это вещи, которые происходят«на стороне», а не во время рендеринга.

В Reactпобочные эффекты обычно должны находиться внутриобработчиков событий.Обработчики событий — это функции, которые React запускает, когда вы выполняете какое-либо действие — например, нажимаете кнопку. Хотя обработчики событий определенывнутривашего компонента, они не выполняютсяво времярендеринга!Поэтому обработчикам событий не обязательно быть чистыми.

Если вы исчерпали все другие варианты и не можете найти подходящий обработчик события для вашего побочного эффекта, вы всё равно можете прикрепить его к возвращаемому JSX с помощью вызоваuseEffectв вашем компоненте. Это говорит React выполнить его позже, после рендеринга, когда побочные эффекты разрешены.Однако этот подход должен быть вашим последним средством.

По возможности старайтесь выражать свою логику только с помощью рендеринга. Вы удивитесь, как далеко это может вас завести!

Deep Dive
Почему React заботится о чистоте?

Итоги

  • Компонент должен быть чистым, то есть:
    • Он занимается своим делом.Он не должен изменять никакие объекты или переменные, существовавшие до рендеринга.
    • Одинаковые входные данные — одинаковый результат.При одинаковых входных данных компонент всегда должен возвращать одинаковый JSX.
  • Рендеринг может произойти в любое время, поэтому компоненты не должны зависеть от последовательности рендеринга друг друга.
  • Вы не должны изменять (мутировать) любые входные данные, которые ваши компоненты используют для рендеринга. Это включает пропсы, состояние и контекст. Для обновления экрана«устанавливайте» состояниевместо изменения существующих объектов.
  • Стремитесь выражать логику вашего компонента в возвращаемом JSX. Когда вам нужно «что-то изменить», обычно вы захотите сделать это в обработчике события. В крайнем случае, вы можете использоватьuseEffect.
  • Написание чистых функций требует некоторой практики, но оно раскрывает силу парадигмы React.

Попробуйте выполнить несколько заданий

Challenge 1 of 3:Почините сломанные часы #

Этот компонент пытается установить CSS-класс для <h1> в значение "night" в период от полуночи до шести часов утра и в "day" во все остальное время. Однако он не работает. Можете ли вы исправить этот компонент?

Вы можете проверить, работает ли ваше решение, временно изменив часовой пояс компьютера. Когда текущее время находится между полуночью и шестью часами утра, цвета часов должны быть инвертированы!