3 различных способа реализации кастомных хуков в React

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

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

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

Одна из ключевых причин этого заключается в том, что разработчики React могут инкапсулировать (небольшие) части логики в свои собственные настраиваемые хуки. В настоящее время это обычная практика в разработке React. Но именно потому, что это настолько распространено, задумывались ли вы когда-нибудь о том, насколько удобочитаемым является ваш способ создания пользовательских хуков?

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

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

Ничего не абстрагируйте.

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

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

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

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

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

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

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

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

  • ✅ Все находится в одном месте.
  • ✅ Отлично подходит для объединения логики и пользовательского интерфейса в небольших компонентах.
  • ⛔ Код становится более сложным по мере увеличения числа логических потоков в компоненте.
  • ⛔ Расстояние по вертикали между кодом, обрабатывающим логику, и пользовательским интерфейсом может стать довольно большим, что в конечном итоге ухудшит удобочитаемость.

Предоставление ограниченного поведения как API

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

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

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

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

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

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

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

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

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

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

  • ✅ Сохраняет размер содержащегося компонента.
  • ✅ Придает явное имя поведению логики и самой логике.
  • ✅ Легче поместить разные логические потоки в перспективу других потоков.
  • ⛔ Сложнее разделить логические потоки.
  • ⛔ Использование настраиваемой ловушки описывает поведение, но не то, как работает основная логика.
  • ⛔ Код для логического потока распределен по нескольким местам.

Поместите все в специальный крючок

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

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

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

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

Последнее преимущество связано с разделением кода для логики и пользовательского интерфейса. При таком использовании пользовательских хуков пользовательский хук отвечает за все, что связано с логикой. Как уже упоминалось, единственное, что он возвращает, - это элементы, необходимые для правильной работы пользовательского интерфейса. Таким образом, API минимален и обеспечивает четкое разделение ответственности.

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

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

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

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

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

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

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

Последние мысли

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

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

Если вам понравилась эта статья, рассмотрите возможность проверки других статей из серии Readable React, моего Twitter для будущих обновлений или некоторых других моих работ по React на Medium.

Учить больше