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

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

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

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

Пример из практики

Компонент, который мы собираемся изучить, представляет собой простой блок «Читать дальше».

Интерфейс

  • children: контент, который следует обернуть (должен быть узел React, чтобы мы могли прикрепить к нему ref).
  • height: максимальная высота перед переносом содержимого.

Поведение

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

Примечания

Контент, отображаемый внутри оболочки «Подробнее», может со временем меняться, например, после асинхронной загрузки.

Компонент методов класса и жизненного цикла

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

Как более подробно объясняется в этом ответе Дэна Абрамова на StackOverflow, ref гарантированно устанавливается перед componentDidMount и componentDidUpdate.

Есть две переменные состояния: одна для отслеживания того, нажал ли пользователь кнопку «Подробнее», а другая устанавливается в shouldWrapIfTooTall, которая, в свою очередь, вызывается в обоих componentDidMount и componentDidUpdate методы жизненного цикла.

Рефакторинг хуков

Чтобы преобразовать наш исходный компонент класса в функциональный компонент, нам понадобится

  • useState для замены переменных состояния нашего компонента isWrapped и hasUserUnwrapped
  • useEffect для замены методов жизненного цикла компонента componentDidMount и componentDidUpdate
  • useRef для создания ссылки для присоединения к дочерним реквизитам.

После рефакторинга компонента для использования новых хуков результат будет следующим:

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

Причина, по которой это происходит, заключается в том, что useEffect не вызывается при изменении свойства дочерних элементов (и, следовательно, contentRef). Несмотря на передачу contentRef в качестве зависимости от ловушки useEffect, эффект некорректно отслеживает изменение свойства current.

Таким образом, интуиция подсказывает, что для решения проблемы все, что нужно сделать, это добавить замену contentRef на contentRef.current в useEffect зависимость. Вы можете увидеть разницу фиксации здесь

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

React Hook useEffect имеет ненужную зависимость: «contentRef.current». Либо исключите его, либо удалите массив зависимостей. Изменяемые значения, такие как contentRef.current, не являются допустимыми зависимостями, поскольку их изменение не приводит к повторной визуализации компонента.

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

Находясь все еще на раннем этапе нашего пути к хукам и узнавая, как справиться с этим, мы стараемся обращать внимание на предупреждения, выдаваемые React, потому что мы предполагаем, что они были добавлены для предотвращения неожиданного поведения, так как мы можем реорганизовать наш компонент? удалить contentRef.current из массива dependencies, чтобы наш компонент работал должным образом?

Ответ на этот вопрос требует 2 небольших изменений в реализации нашего компонента:

  1. Замените contentRef.current в useEffect вспомогательной переменной
  2. Замените useRef ссылкой обратного вызова, которая отвечает за установку значения новой переменной contentHeight в состоянии

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

Вы представляете, как мы могли бы сделать это по-другому? Любой вопрос? Пожалуйста, оставьте комментарий, и я постараюсь помочь, как могу!

Ознакомьтесь с полным кодом для реализации useEffect со ссылками в репозитории GitHub. Тогда хлопайте и оставьте комментарий ниже!

Eatwith объединяет людей через еду за столиками по всему миру. Чтобы получить больше статей и идей от специалистов по технологиям и продуктам, подпишитесь на блог Tech @ Eatwith.