Хабрахабр

[Перевод] Разбираемся с перехватчиками в React

Привет, Хабр!

Мы с чувством невероятной гордости и облегчения сегодня вечером сдали в типографию новую книгу о React

В книге, которую мы сами уже ждем с нетерпением, об этом рассказано в 5-й главе.
На прошлой неделе мы с Софи Олперт представили на конференции React Conf концепцию «перехватчиков», после чего последовал подробный разбор темы от Райана Флоренса. По этому поводу предлагаем вам немного сокращенный перевод статьи Дэна Абрамова (Dan Abramov), рассказывающего об использовании перехватчиков в 16-й версии React.

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

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

Зачем нужны перехватчики?

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

  • Гигантские компоненты, которые сложно рефакторить и тестировать.
  • Дублирование логики между различными компонентами и методами жизненного цикла.
  • Сложные паттерны, в частности, рендеринг свойств (render props) и компоненты высшего порядка.

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

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

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

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

Не разбухает ли React из-за перехватчиков?

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

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

5kB (min+gzip). Что касается увеличения размеров реализации, приложение React при поддержке перехватчиков увеличивается всего лишь примерно на ~1. Нижеприведенный пример слегка экстремален, зато он доходчиво демонстрирует, почему все именно так (щелкните, чтобы развернуть весь тред): Притом, что и само по себе это не слишком много, весьма вероятно, что при использовании перехватчиков размер вашей сборки даже уменьшится, поскольку код перехватчиков обычно минифицируется лучше, чем эквивалентный код с использованием классов.

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

Что же это такое — перехватчики?

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

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

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

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

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

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

Покажите же код!

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

gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae

Мы используем ширину окна внутри нашего компонента, и React переотрисовывает ваш компонент, если он изменится. Если вы читаете этот код, значит, он делает ровно то, что в нем написано. Именно за этим и нужны перехватчики – чтобы сделать компоненты подлинно декларативными, даже если в них содержится состояние и побочные эффекты.

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

gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae

Мы можем использовать их непосредственно из наших компонентов, либо собирать из них собственные перехватчики, например, useWindowWidth. Как показано выше, встроенные перехватчики React вроде useState и useEffect служат «кирпичиками». Использование собственных перехватчиков представляется не менее идиоматичным, чем работа со встроенным API React.

Подробнее о встроенных перехватчиках рассказано в этом обзоре.

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

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

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

codesandbox.io/s/ppxnl191zx

(Этот пример подробнее разобран в данном руководстве.)

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

А что же насчет классов?

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

Они также достаточны для определения компонентов в целом, поскольку предоставляют нам необходимые возможности – например, состояние. Оказывается, что такие встроенные перехватчики удобны не только при создании собственных перехватчиков. У нас в Facebook используются десятки тысяч компонентов классов и нам (точно как и вам) совершенно не хочется их переписывать. Вот почему мы хотели бы, чтобы в будущем перехватчики стали основным средством для определения компонентов React.
Нет, мы не планируем постепенно упразднять классы. Перехватчики покрывают все те практические случаи, в которых используются классы, но дают большую гибкость при извлечении, тестировании и переиспользовании кода. Но, если сообщество React приступит к использованию перехватчиков, станет нецелесообразно сохранять два рекомендуемых способа написания компонентов. Вот почему мы связываем с перехватчиками наши представления о будущем React.

А вдруг перехватчики – это магия?

Возможно, правила перехватчиков вас озадачат.

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

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

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

Как пишет Джейми, она получается примерно такой: Обратите внимание: и на уровне реализации перехватчиков никакой магии тоже нет.

gist.github.com/gaearon/62866046e396f4de9b4827eae861ff19

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

(В этой статье от Руди Ярдли все красиво объяснено в картинках!)

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

Поэтому, можно утверждать, что в перехватчиках меньше магии, чем в других популярных подходах к решению таких задач. Перехватчики не зависят от прокси и геттеров, которые так распространены в современных библиотеках JavaScript. Не больше, чем в array.push и array.pop (в случае с которыми порядок вызовов также важен!)

На самом деле, уже через несколько дней после публикации предложения самые разные люди показали нам экспериментальные реализации такого же API перехватчиков для Vue, веб-компонентов и даже для обычных функций JavaScript.
Наконец, если вы фанатично преданы функциональному программированию, и вам неуютно на душе, когда React начинает полагаться на изменяемое состояние как на деталь реализации. Дизайн перехватчиков не привязан к React. Естественно, на внутрисистемном уровне React всегда полагался на изменяемое состояние – а ведь именно этого вы хотели бы избежать. Но, возможно, вас утешит, что обработку перехватчиков вполне можно реализовать и в чистом виде, ограничившись одними только алгебраическими эффектами (если бы в JavaScript они поддерживались).

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть