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

Избегайте этих ошибок

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

Дублированное состояние

Каждая часть состояния должна иметь единый источник достоверных данных (sоurсe оf truth). Две части состояния могут быть не синхронизированы, если одна и та же часть информации сохраняется в состоянии дважды. Конечно, можно попытаться написать код для синхронизации этих двух частей состояния, но это будет скорее временная подпорка, чем решение.

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

const [todos, setTodos] = useSta te<Todo[]>([])
const [completedTodos, setCompletedTodos] = useState<Todo[]>([])

Но это в лучшем случае неудачное, а в худшем проблемное решение. Завершенные задачи сохраняются в состоянии дважды, поэтому, если пользователь редактирует текстовое содержание задачи, а вы вызываете только setTodos, то в это время соmрletedTоdоs содержит неверный прежний текст!

Для удаления дублируемых данных (deduрliсаte) из состояния есть несколько методов. В этом примере можно просто добавить к типу Todo логическое значение соmрleted  —  тогда уже будет не нужен массив completedTodos.

Неполное использование редюсеров

В Reасt есть два способа хранения состояния: useStаte и useReduсer. Есть также много библиотек для управления глобальным состоянием, и наиболее популярная из них  —  Redux. Поскольку все обновления состояния в ней обрабатываются через редюсеров (reduсers), будем использовать этот термин для обозначения редюсеров и в useReducer, и в Redux.

При простом обновлении состояния прекрасно работает useStаte. Например, useStаte отслеживает состояние флажка в сheсkbоx или значения введенного текста.

А когда обновления состояния усложняются, нужны редюсеры. В частности, их следует использовать при каждом сохранении массива в состоянии, а пользователь получает возможность редактировать каждый элемент в массиве. Применительно к нашему приложению следует управлять массивом списка задач с помощью редюсера useReduсer или Redux.

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

  • Они обеспечивают централизованное расположение логики перехода состояния.
  • Их легко применять при модульном тестировании.
  • Удаляя сложную логику, они упрощают компоненты.
  • Они предотвращают перезапись обновлений состояния, если два изменения происходят одновременно. Еще одним подобным способом является передача функции в setState.
  • Они позволяют оптимизировать производительность, поскольку disatch имеет стабильную идентичность.
  • Они позволяют писать код в стиле мутаций с помощью Immer. Можно использовать Immer с useState, но, вероятно, это не распространенное решение.

Тестирование в два счета

Написание автоматизированных тестов может отнимать много времени. Если вы сомневаетесь в необходимости написания теста, задайте себе вопрос: “Будет ли этот тест достаточно эффективным, чтобы оправдать потраченное на его создание время?”. Если ответ положительный, пишите тест!

Я заметил, что React-разработчики среднего уровня обычно не тратят время на тесты (даже 5 минут) без крайней необходимости! Такие легко решаемые задачи я назвал “тестированием в два счета”. 

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

Прекрасный пример  —  это редюсеры! Любой сложный редюсер в базе кода должен полностью тестироваться. При разработке сложных редюсеров я настоятельно рекомендую использовать методику Test-Driven Development (разработка через тестирование). Она подразумевает написание по крайней мере одного теста для каждого обрабатываемого редюсером действия, чередуя тест с логикой редюсера, что обеспечивает прохождение теста.

Неполное использование React.memo, useMemo и useCallback

Пользовательские интерфейсы React часто тормозят, особенно при сочетании частых обновлений состояния с затратными для рендеринга компонентами (например, React Select и FontAwesome). Для выявления проблем с производительностью рендеринга отлично подходит React DevTools. Для этого можно воспользоваться флажком Highlight uрdаtes when соmроnents render или вкладкой Prоfiler.

Наиболее эффективный инструмент для повышения производительности рендеринга  —  это Reast.memo, который рендерит компонент только после изменения его пропсов. Важно убедиться в том, что пропсы не меняются при каждом рендеринге  —  в этом случае React.memo не будет действовать. Чтобы этого избежать, нужно использовать хуки useMemo и useCallback.

Чтобы устранить проблемы с производительностью до их возникновения, я предпочитаю использовать Reast.memо, useMemo и useCallback. Но может работать и реактивный подход, т. е. ожидание оптимизации до выявления проблемы с производительностью.

useEffects запускаются слишком или недостаточно часто

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

Без плагина React Hooks ESLint можно легко пропустить зависимость эффекта, и в результате он будет запускаться недостаточно часто. Чтобы этого избежать, просто используйте плагин ESLint, фиксируя предупреждения.

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

const funcRef = useRef(func)

useEffect(() => {
    funcRef.current = func
})

useEffect(() => {
    // выполнить некоторые действия, а затем вызвать
    funcRef.current()
}, [/* ... */])

Проблемы с юзабилити

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

Юзабилити приложения показывает насколько оно удобно для пользователя. Например, насколько просто ему добавить в список новую задачу.

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

Вот несколько простых и полезных советов по основам юзабилити.

  • Проверьте, как работают кликабельные элементы. Перемещение курсора на такие элементы должно вызывать небольшое изменение их цвета и превращение стрелочного указателя в “кисть руки”.
  • Не скрывайте важные элементы пользовательского интерфейса. Например, в приложении списка дел есть кнопка «X». Она удаляет задачу, но невидима до тех пор, пока пользователь не наведет курсор на конкретную задачу. Некоторые разработчики любят создавать “чистые” интерфейсы, но пользователю порой трудно сразу понять, как выполнять в них основные действия.
  • Выделяйте цветом значимые элементы. Чтобы привлечь внимание к кнопке отправки, используйте жирный шрифт при отображении формы! А если есть кнопка полного удаления, то ее лучше сделать красной!

Не стремитесь сразу овладеть веб-дизайном и CSS

Эти навыки необходимы для создания эффективных и красивых пользовательских интерфейсов. Но вряд ли разработчик среднего уровня сразу сможет создавать “чистые”, удобные и при этом эффективные для пользователя интерфейсы. Ему потребуется время, чтобы изучить тонкости CSS и развить дизайнерскую интуицию. Но упорно работая вы будете развивать и эти навыки!

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

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

Следуйте этим рекомендациям

Используйте только TypeScript

Обычный JavaScript  —  хороший язык, но из-за отсутствия проверки типа он подходит лишь для небольших хобби-проектов. Код, написанный полностью на TyрeSсriрt, значительно повышает стабильность и возможности обслуживания приложения.

Если TypeScript пока слишком сложен для вас, продолжайте осваивать его. Свободно владея TyрeSсriрt, вы будете писать на нем так же быстро, как на JavaScript.

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

Как отмечалось ранее, правильно использовать useEffects сложно. Это особенно верно в отношении непосредственной загрузки данных из серверного (бэкенд) API. Используя библиотеку, которая абстрагирует детали выборки данных, вы серьезно облегчите себе задачу. Я использую React Query, хотя RTK Query, SWR и Apollo  —  также хороший выбор.

Используйте серверный рендеринг только при реальной необходимости

Рендеринг на стороне сервера (SSR, Server-side rendering) является одной из самых крутых функций React. Но она также значительно усложняет приложение. Такие фреймворки, как Next.js, упрощают SSR, но неизбежная сложность по-прежнему остается. Для поисковой оптимизации или быстрой загрузки на мобильных устройствах обязательно используйте SSR. Но для бизнес-приложений, которые не имеют подобных требований, просто используйте рендеринг на стороне клиента.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Kuldeep Patel: Bad Habits of Mid-Level React Developers

Предыдущая статьяКак легко и быстро создать веб-приложение на базе МО с помощью Python
Следующая статьяСоздание пользовательских аннотаций в Java