React. Лёгкий способ бросить курить

Писать кнопочки и формочки на React — дело не хитрое. Но почти всегда фронтовые проекты превращаются в нечто трудночитаемое и едва ли поддерживаемое. Визуально различия проектов на React и JQuery со временем сохраняются, а вот developer experience с точки зрения трудозатрат на поддержку становится примерно одинаковым.

За лесом кнопок, эффектов и пропсов не разглядеть сценария

JavaScript на то и script, что на нём хорошо писать пользовательские сценарии. React — отличная ui-библиотека. Главное не перепутать. Поддержка пользовательских сценариев описанных через ui-библиотеку — для сильных духом.

Привожу пример. Пожалуй самый распространённый случай — это извращенная интерпретация принципа DRY. Теперь поподробнее: речь идёт про случаи, когда имеется чёткий триггер, например, клик по кнопке, ввод текста в инпуте, но сценарий обработки этого триггера находится в useEffect. Почему в useEffect, а не в onClick или onChange? Ну как же, ведь этот же сценарий запускается ещё и при изменении вот того пропса. Не писать же одно и то же дважды.

Мы перестали оперировать понятиями функций и аргументов. Вместо них мы используем эффекты и пропсы

Чем дальше в лес, тем больше дров. Со временем этот useEffect станет «местом силы». Всё больше и больше пользовательских сценариев будут использовать этот хук, массив зависимостей начнёт расти, а внутри хука появятся инструкции if с хитрыми условиями в скобках. Ну и вишенка на торте — понимание, что без хука наподобие usePrevious ничего не заработает. Не всегда usePrevious показатель неправильно выбранного тригера, но в 9 из 10 случаев вам есть о чём задуматься.

Всего два простых правила

  1. кратчайший путь для сценария

  2. изоляция сценария

Кратчайший путь как раз и начинается с выбора правильного тригера. Например, можно сравнить два варианта из сценария проверки на валидность текста. Есть input, есть стейт value и есть стейт isValid.

Сценарий 1:

  1. onChange устанавливает значение value в стейт

  2. затем useEffect реагирует на изменение value, проверяет его на валидность и устанавливает в стейт значение isValid

Сценарий 2:

  1. onChange вызывает обработчик события с аргументом value, который и установит в стейт value и isValid

Не стоит забывать, что фреймворк — это история про инверсию управления. В данном случае сценарий 2 короче сценария 1 не вдвое, а сильно больше. Ведь во втором случае, при выполнении нашего сценария мы не передавали управление react’у. В первом случае react выполнил один лишний рендер, возможно запустил ещё какие-то хуки, хотя нам это и не надо.

Разрывы в пользовательском сценарии — источник неприятностей

Так мы плавно подходим ко второму пункту — изоляции. Допустим в компонент приходит пропс, который также влияет на значение isValid, тогда для того, чтобы развести два этих сценария достаточно просто описать функцию валидации и передать её в useEffect и в onChange.

Старайтесь писать так, чтобы useEffect был триггером пользовательского сценария, а не его промежуточным звеном

Всё это поможет другим разработчикам не плутать от useEffect’а к useEffect’у по вашему коду, а зацепившись за триггер последовательно «размотать» сценарий.

Обратная сторона

Мы рассмотрели ситуацию, когда логика приложения просачивается в инструменты построения ui. Теперь рассмотрим второй частый случай, когда внутренности ui протекают в модель приложения.

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

О протечке нам просигнализирует появление в сторе (или в верхнеуровневом компоненте) поля, например, popout: { type, data }. Далее в коде можно обнаружить компонент с немаленькой инструкцией switch, который только и ждет, когда в поле popout положат данные. И вот он подбирает нужный компонент (согласно type), передаёт ему data и сценарий продолжается.

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

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

Косательно примера с todo-листом можно поместить компонент popout внутри компонента ячейки с запланированным действием и при нажатиии на кнопку «удалить» показывать popout через портал. Каждая ячейка имеет свой popout, а значит рассказывает максимально короткий и изолированный сценарий. Для простоты можете использовать библиотеку «react-portal» — он прикрепляет содержимое к document.body. Это не плохо подойдёт для всплывающих окон. Для сценариев, где layout сложный и прикрепление компонента к document.body не отвечает потребностям, я написал библиотеку «react-jsx-portal» (ссылка). Вторая часть readme на русском. Я думаю многим она подойдёт.

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