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

Остальные статьи этой серии следующие:

Если вы еще не прочитали первые три, я бы порекомендовал хотя бы прочитать первую и третью части. Эта часть в значительной степени опирается на обсуждение замыканий и зависимостей, обсуждаемых в части третьей, а часть первая предоставит вам обзор различий.

Четвертая часть - действительно самая интересная и разнообразная часть функциональных компонентов. Используя компоненты на основе классов, вы создаете побочные эффекты в componentWillMount и componentDidUpdate и очищаете их в componentWillUnmount. С помощью функциональных компонентов вы создаете и разбиваете отдельные побочные эффекты с помощью useEffect и useLayoutEffect. Парадигмы очень разные. Настолько разные, что, когда я слышу: «Как я могу воспроизвести componentWillMount / componentDidUpdate с помощью крючков?» Я спрашиваю: «Зачем вам это нужно?»

Давайте начнем с введения хуков, а затем разберемся, почему они меняют наше представление о побочных эффектах к лучшему.

useEffect: ваш постоянный создатель побочных эффектов

Вместо того, чтобы создавать побочный эффект в componentWillMount / componentDidUpdate методах класса, функциональные компоненты используют useEffect Hook.

Давайте посмотрим, как useEffect используется для настройки 5-секундного обновления оценки пользователя:

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

Само закрытие - это побочный эффект, который вы хотите создать. С useEffect закрытие запускается после рендеринга DOM и согласования всех ссылок.

useEffect побочные эффекты выполняются асинхронно после первой отрисовки страницы. Это критическое отличие от componentDidMount, который выполняется синхронно перед первой отрисовкой. Асинхронный характер useEffect делает страницу значительно более отзывчивой, чем при создании побочных эффектов с помощью componentDidMount.

Замыкание useEffect может возвращать функцию. Эта возвращенная функция позволяет устранить побочный эффект закрытия. Он будет запускаться каждый раз при изменении массива зависимостей и при отключении компонента. Обратите внимание: поскольку его можно определить как замыкание, созданное эффектом, он может использовать любые переменные, определяемые эффектом при его выполнении.

Последний элемент, массив зависимостей, сообщает useEffect, когда следует очистить предыдущее закрытие и перезапустить его с обновленными значениями. Если вы предоставите пустой массив зависимостей, React знает, что ничего не изменит этот эффект. В этом случае он будет запущен один раз при первом создании компонента, и любая предоставленная функция очистки будет запускаться только после размонтирования компонента.

Итак, если посмотреть на пример выше, закрытие useEffect создает интервал, который обновляет счет пользователя каждые 5 секунд. Этот побочный эффект зависит от userId. Каждый раз, когда userId обновляется, возвращенная функция закрытия очищает существующий интервал опроса, используя его закрытие для получения intervalId, созданного начальным эффектом. После очистки эффект запускается снова, используя новое значение userId для генерации другого интервала опроса. Когда компонент наконец размонтируется, возвращенная функция очистки вызывается в последний раз, чтобы очистить этот последний интервал.

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

useLayoutEffect: используйте его, если необходимо

Итак, если useEffect является средством создания побочных эффектов, почему useLayoutEffect?

Если useEffect генерирует побочные эффекты асинхронно после первой отрисовки, useLayoutEffect генерирует побочные эффекты синхронно, до первой отрисовки. Это может быть полезно, если часть презентации мерцает при первом рендеринге компонента, но чаще всего это не проблема.

Поскольку useLayoutEffect выполняется синхронно перед рисованием, это может существенно повлиять на скорость отклика вашего приложения. Сложные вычисления или инициализация ввода-вывода сделают ваше приложение медленным и запаздывающим, если они выполняются в слишком большом количестве мест. Это основная проблема с компонентами на основе классов, которые используют componentDidMount, который также является синхронным. useEffect был создан специально для улучшения этой ситуации.

Итак, если вы видите это мерцание, и оно вас раздражает, используйте useLayoutEffect. В противном случае useEffect сделает ваше приложение более плавным.

Итак, я солгал. Вы можете вроде воссоздать componentDidMount/componentDidUpdate. Но переход от модели создания синхронных побочных эффектов, ориентированной на жизненный цикл, не имеет смысла, если вы можете создавать побочные эффекты, которые фокусируются на побочном эффекте и не прерывают отзывчивость компонента. Следующий раздел посвящен деталям.

Почему побочные эффекты функциональных компонентов не работают как компоненты на основе классов

Так почему же useEffect и useLayoutEffect вызывают такое замешательство у разработчиков, привыкших к компонентам на основе классов?

В компоненте на основе классов у вас есть три места, где можно справиться с побочными эффектами:

  • componentDidMount - после первого рендеринга DOM
  • componentDidUpdate - после рендеринга модели DOM в течение ее жизненного цикла
  • componentWillUnmount - до разрушения компонента

Это заставляет разработчиков думать в следующих терминах:

  • Какие побочные эффекты я хочу вызвать при запуске компонента?
  • Как мне изменить компонент или изменить его побочные эффекты в течение его жизненного цикла?
  • Как мне очистить все, когда компонент будет готов?

С _39 _ / _ 40_ побочные эффекты ставятся в очередь при первом рендеринге компонента, очищаются при отключении компонента, очищаются и воссоздаются при изменении их зависимостей. Кроме того, при изменении зависимости эти Хуки дают вам возможность убрать побочные эффекты в контексте создаваемого побочного эффекта. Это заставляет разработчиков думать в следующих терминах:

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

Побочные эффекты, связанные с классом, обычно зависят от его жизненного цикла. Побочные эффекты, связанные с крючками, обычно основаны на их жизненных циклах.

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

Допустим, у вас есть 3 разных побочных эффекта: A, B и C. Перед отключением компонента необходимо очистить A и B. Кроме того, B необходимо обновить, если свойство изменяется.

С компонентом на основе классов вы можете получить что-то вроде этого:

Это настолько чисто, насколько это возможно. Создание и очистка побочных эффектов разделены на отдельные функции, что упрощает чтение функций жизненного цикла, но побочные эффекты по-прежнему распространяются на эти функции. Было довольно легко пропустить, что побочный эффект C был запущен, но не нуждался в очистке.

С функциональными компонентами ловушка useEffect инкапсулирует всю информацию о конкретном побочном эффекте. Тот же сценарий выше выглядит так:

Здесь первый Hook посвящен исключительно побочному эффекту A - его созданию и очистке. Второй крючок посвящен созданию, очистке и зависимостям побочного эффекта B. Третий посвящен созданию побочного эффекта C.

Поскольку каждый побочный эффект обновляет состояние компонента независимо, мы можем позволить компоненту повторно визуализироваться при каждом обновлении, позволяя побочным эффектам заботиться только о себе. Если один побочный эффект зависит от результатов другого побочного эффекта, он может идентифицировать состояние, на которое влияет второй эффект, как зависимость, в результате чего первый эффект сбрасывается при изменении этого состояния.

Побочные эффекты на основе хуков разработаны для элегантного управления, инкапсуляции и изоляции изменений внешнего состояния. Изменения могут быть разрозненными или взаимозависимыми, но зависимости четко указаны сразу после самого побочного эффекта.

Используйте ссылки как аварийный люк для закрытия

Иногда вы создаете побочный эффект, который фактически требует самой последней информации из модели DOM или других побочных эффектов. У вас может быть такое частое обновление данных, что React будет наводнен повторными рендерами и сбросами побочных эффектов, если вы обновите состояние для обновления своих эффектов. В обстоятельствах, когда вам нужна постоянная обратная связь, используйте исх.

Сами судьи неизменны. Их даже не нужно включать в массив зависимостей. (React даже пожалуется, если вы попытаетесь). Фактически, ссылки нельзя использовать для запуска повторного рендеринга, даже если вы попытаетесь сделать его поле current зависимостью. (React тоже будет жаловаться на это.) Однако поле current ссылки доступно и всегда может быть обновлено и прочитано в этот момент.

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

Установив scrollTopRef.current в обработчике событий прокрутки, он будет постоянно обновляться, независимо от того, сколько и как быстро происходит прокрутка, без принудительного повторного рендеринга или обновлений. Когда дочерний компонент обновляется, обработчик событий удаляется, а после рендеринга нового дочернего элемента scrollTop ползунка сбрасывается до того места, где он был до потери своего предыдущего дочернего элемента, и добавляется новый обработчик событий.

(По общему признанию, я понятия не имею, действительно ли это необходимо. Сейчас полночь, и мне просто нужен был пример, который проиллюстрировал, как значения могут быть обновлены и использованы без повторной визуализации.)

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

tl;dr

Надеюсь, эта статья проиллюстрировала большую разницу между основанными на классах и функциональными компонентами: как они управляют побочными эффектами.

  • Побочные эффекты компонентов на основе классов сосредоточены на жизненном цикле компонента. Побочные эффекты, создаваемые useEffect / useLayoutEffect, сосредоточены на жизненном цикле побочных эффектов.
  • useEffect позволяет нам создавать побочные эффекты асинхронно, улучшая отзывчивость приложений.
  • Массивы зависимостей позволяют нам сбрасывать и обновлять наши побочные эффекты на основе измененных значений.
  • Ссылки - это выход, позволяющий обновлять значения без сброса побочных эффектов.

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

Было очень весело писать об этом. Я ценю, что вы поддерживали меня в этих романах. Надеюсь, вы сочли это полезным!