- PVSM.RU - https://www.pvsm.ru -
Автор материала, перевод которого мы публикуем сегодня, говорит, что ему очень и очень часто приходилось видеть, как веб-разработчики бездумно пользуются современными фреймворками вроде React, Angular или Vue.js. Эти фреймворки предлагают много интересного, но, как правило, программисты, применяя их, не учитывают главнейшей причины их существования. Обычно на вопрос: «Почему вы используете фреймворк X», можно услышать следующие ответы, среди которых, однако, нет самого главного:
На самом же деле самая главная, фундаментальная причина использования фреймворков заключается в том, что они помогают решать задачу синхронизации пользовательского интерфейса и внутреннего состояния приложения. Это — чрезвычайно сложная и важная задача, и именно о ней мы сегодня и поговорим.
Представим, что вы создаёте веб-приложение, пользуясь которым пользователь может разослать множеству людей некие материалы, вроде приглашений, введя на особой странице адреса их электронной почты. Вводя адрес в поле, пользователь нажимает клавишу Enter и приложение сохраняет введённый адрес и выводит его на странице. UX/UI-дизайнер принял следующее решение: если в состоянии приложения ничего нет — мы показываем на странице поле ввода со вспомогательным текстом, в других случаях выводится такое же поле ввода и уже введённые адреса электронной почты, правее каждого из которых расположена кнопка или ссылка для удаления адреса.
Два состояния формы — пустая форма, и форма с адресами
В качестве хранилища состояния такой формы может использоваться нечто вроде массива объектов, каждый из которых содержит адрес электронной почты и некий уникальный идентификатор. Добавление в приложение нового адреса путём ввода его в поле и нажатия на клавишу Enter приводит к помещению этого адреса в массив и к обновлению пользовательского интерфейса. При щелчке по ссылке delete
адрес удаляется из массива, и, опять же, обновляется интерфейс приложения. Несложно заметить, что каждый раз, когда меняется состояние приложения, нужно обновить пользовательский интерфейс.
Почему именно этот момент особенно важен? Для того чтобы в этом разобраться, попробуем реализовать подобную функциональность на чистом JavaScript и с использованием какого-нибудь фреймворка.
Вот [2] реализация рассмотренного выше интерфейса на чистом JS, подготовленная с помощью CodePen.
Глядя на код этой реализации, можно оценить объём работы, необходимый для того, чтобы создать подобный интерфейс и внутренние механизмы приложения без применения каких-либо вспомогательных средств (кстати, использование классических библиотек, вроде jQuery, приведёт к примерно такому же процессу разработки и результату).
В этом примере HTML формирует статическую структуру страницы, а динамические элементы создаются средствами JavaScript (с помощью document.createElement
) Это приводит нас к первой проблеме: JS-код, который описывает пользовательский интерфейс, не отличается хорошей читаемостью, и, используя и его, и HTML, мы разбиваем определение элементов интерфейса на две части. Мы могли бы в подобной ситуации использовать innerHTML
, то, что получилось бы, отличалось бы большей понятностью, но такой подход менее эффективен и может приводить к проблемам из области межсайтового скриптинга.
Тут можно было бы воспользоваться движком для работы с шаблонами, но если нужно воссоздать большое поддерево DOM, это приводит к ещё двум проблемам. Во-первых, это не особенно эффективно, во-вторых — при таком подходе обычно приходится повторно подключать обработчики событий.
Однако среди вышеописанных проблем нет главной, которая заключается в том, что пользовательский интерфейс нужно обновлять при каждом изменении состояния приложения. При каждом обновлении состояния требуются серьёзные усилия, выраженные соответствующим кодом, направленные на обновление пользовательского интерфейса. Например, в нашем случае, при добавлении адреса электронной почты, нужно 2 строки кода для того, чтобы обновить состояние, и больше десятка строк — для того, чтобы обновить интерфейс. При этом надо учитывать, что интерфейс в нашем примере максимально упрощён.
Код, необходимый для обновления состояния и пользовательского интерфейса приложения
Такой код не только сложно писать и поддерживать. Важнее то, что он весьма ненадёжен. Его очень легко «поломать». Представьте себе, что нам надо реализовать возможность синхронизации списка адресов с сервером. Для этого понадобится сравнивать данные, которые имеются в приложении, с тем, что получено с сервера. Тут придётся сравнить все идентификаторы объектов, использующихся для хранения адресов, и сами адреса, так как в приложении вполне могут быть копии записей с теми же идентификаторами, что и у записей на сервере, но с другими адресами.
Для этого понадобится создать большой объём узкоспециализированного кода для организации эффективного изменения DOM. При этом, если будет допущена малейшая ошибка, синхронизация интерфейса с данными нарушится. Это может выражаться в отсутствии неких данных на странице, в показе неправильной информации, в появлении элементов управления, которые не реагируют на воздействия пользователя, или, что ещё хуже, вызывают выполнение неправильных действий (например — кнопка для удаления элемента воздействует не на тот элемент, которому она соответствует в интерфейсе).
В результате поддержка синхронизации интерфейса и данных подразумевает написание большого объёма однообразного и ненадёжного кода. Что же делать? Обратимся к декларативному описанию интерфейсов.
Решением проблемы является не мощное сообщество некоего фреймворка, не дополнительные инструменты, не экосистема и не сторонние библиотеки. Основная возможность фреймворков, которая позволяет решить вышеописанную проблему, заключается в том, что они позволяют реализовывать интерфейсы, которые гарантированно будут синхронизированы со внутренним состоянием приложения.
На самом деле, выше описан идеальный вариант решения проблемы, на самом же деле, это так в том случае, если разработчику не приходится ограничивать себя некими правилами, которые может иметь конкретный фреймворк (например — правилом, касающимся иммутабельности состояния приложения).
Используя фреймворк, мы определяем интерфейс за один заход, не имея необходимости писать код для работы с интерфейсом при выполнении каждого действия. Это всегда даёт один и тот же видимый результат, основанный на конкретном состоянии: фреймворк автоматически обновляет интерфейс после изменения состояния.
Существуют две основных стратегии синхронизации интерфейса и состояния.
React, Angular и Vue.js часто сравнивают с веб-компонентами [3]. Это демонстрирует тот факт, что многие не понимают главной возможности, которую предоставляют разработчику эти фреймворки. Это, как мы уже выяснили — синхронизация интерфейса и внутреннего состояния приложения. Веб-компоненты таких возможностей не дают. Они дают разработчику тег <template> [4], но не механизм согласования интерфейса и состояния. Если программист пользуется веб-компонентами и поддерживает синхронизацию интерфейса и состояния приложения, ему придётся решать эту задачу самостоятельно, или воспользоваться чем-то вроде Stencil.js [5] (эта библиотека, как и React, использует Virtual DOM).
Отметим, что сила подобного решения — не в использовании веб-компонентов, а в возможности поддерживать синхронизацию интерфейса и состояния. Веб-компоненты не содержат встроенных средств для такой синхронизации. Тут придётся либо пользоваться сторонней библиотекой, либо делать всё вручную. На самом деле, невозможно создать сложный, эффективный и простой в плане поддержки интерфейс на чистом JavaScript. Именно это и является основной причиной, по которой разработчикам необходимы современные фреймворки.
Поэкспериментируем, перепишем наш пример [2], который основан на чистом JS, пользуясь реализацией [6] Virtual DOM, при этом не будем прибегать к применению фреймворков. То, что у нас получится, вполне можно назвать нашим собственным фреймворком. Вот его основа, базовый класс, на котором будут основаны компоненты.
Ядро современного фреймворка собственной разработки
Вот — переписанный компонент AddressList (тут, для поддержки JSX, применяется babel).
Интерфейс приложения, реализованный средствами фреймворка собственной разработки
Теперь пользовательский интерфейс определён в декларативном стиле, при этом мы не пользовались существующими фреймворками. Мы можем реализовать новую логику, которая меняет состояние так, как нам надо, при этом не нужно писать дополнительный код для того, чтобы поддерживать синхронизацию интерфейса и состояния приложения. Проблема решена!
Теперь, за исключением обработчиков событий, всё это очень похоже на React-приложение. Тут имеются, например, методы render()
, componentDidMount()
, setState()
. После того, как решена базовая проблема синхронизации интерфейса и внутреннего состояния приложения, другие возможности интегрируются в полученную систему совершенно естественным образом.
Полный код этого проекта можно найти в этом [7] GitHub-репозитории.
Подводя итоги, мы можем сделать следующие выводы:
Уважаемые читатели! Как по-вашему, действительно ли синхронизация интерфейса и состояния приложения является главной проблемой, которую решают современные фронтенд-фреймворки?
Автор: ru_vds
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/277329
Ссылки в тексте:
[1] Image: https://habrahabr.ru/company/ruvds/blog/353074/
[2] Вот: https://codepen.io/gimenete/pen/vRZLrq
[3] веб-компонентами: https://www.webcomponents.org/
[4] <template>: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
[5] Stencil.js: https://stenciljs.com/
[6] реализацией: https://github.com/Matt-Esch/virtual-dom
[7] этом: https://github.com/gimenete/ui-state-sync
[8] Источник: https://habrahabr.ru/post/353074/?utm_source=habrahabr&utm_medium=rss&utm_campaign=353074
Нажмите здесь для печати.