-
Notifications
You must be signed in to change notification settings - Fork 9
React, Vue.js и elu.js
Текст этой статьи написан по итогам 10-дневного ознакомления автора библиотеки elu.js с React и Vue.js на примере Ant Design Pro и Ant Design Pro of Vue соответственно.
Автор никоим образом не претендует ни на широту охвата, ни на экспертизу в области современных информационных технологий.
Однако настоящий обзор может пригодиться некоторым разработчкам Web-интерфейсов -- хотя бы в качестве стартового набора ссылок.
В основном здесь рассматриваются базовые javaScript-библиотеки для разработки Web-интерфейсов, где
- вообще существенная часть логики реализуется на клиенте;
- в частности, отдельные области экрана общаются с сервером и обновляются асинхронно и независимо друг от друга.
Итак:
- Angular - платформа, с 2016 года продвигаемая Google, взамен ранее раскручивавшейся AngularJS, ныне отправленной на заслуженный support. Данное ПО упомянуто здесь лишь вскользь. Общедоступные проекты Google, базирующихся на Angular, автору не известны.
- React - платформа, развиваемая с 2013 года и используемая Facebook для своего основного продукта и довольно широко известная за пределами корпорации.
- Vue.js - "движок", разрабатываемый с того же, 2013-го, года, ушедшим на вольные хлеба бывшим сотрудником Google Эваном Ю (Ю Юси), получившим на старте богатый (отрицательный) опыт использования AngularJS. По состоянию на конец 2020 года, был достаточно размаркетирован для того, чтобы молодые люди, прошедшие краткие курсы компьютерной грамотности, на интервью прямо с порога требовали как следует серьёзных денег, уточняя: "Я только Vue!".
- elu.js - библиотечка на ту же тему, придуманная автором этих строк в 2017 году и используемая, по всей видимости, только в ближайшем кругу его коллег.
- Ant Design, или antd -- Web-дизайн собственной информационной системы Ant Group: оператора мощнейшей в КНР китайской независимой платёжной сети Ali Pay (у которой, правда, в последнее время большие проблемы), реализованный в виде набора низкоуровневых React-компомент: кнопки, поля ввода и т. п.
- Ant Design Pro -- набор высокоуровневых компонент в дополнение к antd: "верхняя обвязка" с собственной системой страниц-вкладок и т. п. Как и базовый antd, представляет собой набор компонент React, однако их реализация существенно использует Angular.
- Ant Design of Vue -- аналог antd с заменой React на Vue. Вообще оригинальный antd использует React не как API, а как платформу для реализации, поэтом в данном случае речь идёт не о привязке одной библиотеки к двум платформам, а о практически полном переписывании.
- Ant Design Pro of Vue -- соответственно, Ant Design Pro, переписанный с React (+ Angular) на Vue.js. По заявлению на демо-сайте, лидирует в области Web-дизайна во всём округе округе Си Ху. Документирован в основном на китайском языке.
И в React, и во Vue.js, и в elu.js основное понятие: "компонента", то есть в основном кусок кода, знающий своё место на экране браузера (как правило, прямоугольник) и умеющий его обновлять в соответствии с заданным (серверным) источником данных в ответ на события ввода.
Первые версии React относятся к 2013 году: тогда Facebook как раз исполнился 1 млрд пользователей и прямо по курсу было падение чистой прибыли год-к-году на 95%. Offtopic: JDK 1.8 вышел только в следующем году. В общем, вполне естественно, что за основу была взята идеология ООП во всей её красе.
Не помешало даже то, что ES6, то есть javaScript с ключевым словом class
, вышел только в 2015-м.
Итак, изначально компоненты React -- это классы, восходящие к общему предку React.Component
.
Вдоволь усладившись всей мощью объектно-ориентированного подхода, разработчики React всего за 5 лет пришли к тому же, на что java-гуру потребовалось впятеро больше времени: к функционально-ориентированному программированию (по иронии судьбы, заложенном в javaScript изначально). React-компоненты в новых проектах, хотя по сути остаются экземплярами таких же классов, в коде выглядят как результаты вызова функций.
И ведь как в старом javaScript ещё с лихих 90-х: слегка типизированный "объект" -- был результатом в общем-то почти обычного (слегка помеченного ключевым словом new
) вызова ничем не примечательной функции. Которая в контексте вызова с new
обретала роль "конструктора" и заодно "класса". Правда, при этом в методах объекта на выходе он сам, инкапсулированный кусочек данных, был доступен как this
.
Однако в функциональных компонентах React ситуация несколько иная: вернувшись от ООП к ФОП, там утеряли this
. Копенсирующий это специальный API ("Hooks") выглядит на первый взгляд не вполне очевидно. Но ко всему можно привыкнуть. Наверное.
По состоянию на сегодня, 10.01.2021, рабочая версия Vue.js -- 2.х. В ней компоненты являются "объектами" в традиционном для js смысле: результатами new Vue ({...})
, то есть вызова общего конструктора с широчайшим набором разнообразных опций.
Во Vue.js 3.х введено новшество: Composition API -- формально во многом повторяющее React Hooks. Однако здесь, по всей видимости, речь не идёт о столь же масштабной смене парадигмы. Просто добавляется очередная возможность писать более компакный код (коих во Vue уже изрядное число).
В elu.js компонента, или блок -- это побочный результат вызова функции show_block (устаревший вариант: use.block), который нигде впоследствии не существует и не может использоваться как переменная. Тип компоненты -- это лишь строка, фигурирующая в именах нескольких файлов и функций, связанных чисто тематически.
Это можно считать дефектом архитектуры. Однако, во всяком случае, это результат осознанного выбора: поскольку основной смысл elu.js -- организация разработки с максимальным разделением кода, обрабатывающего содержимое и его представление.
Исходные тексты компонент React (в основном) пишутся не на чистом js, а с расширением JSX. На первый взгляд это смотрится как javaScript вперемежку с HTML. На второй -- становится заметно, что это, по крайней мере, XHTML. На третий -- XML с некоторыми знакомыми тегами. И, наконец, переменные в фигурных скобках наводят на мысль о том, что мы имеем дело с клиентским аналогом JSF.
На самом деле каждый *ML-подобный тег прекомпилируется в js-код создания соответствующей компоненты, где значения атрибутов и дочерних объектов передаются в качестве параметров конструктору.
Vue.js реализует свой язык разметки, который довольно близок к стандартному HTML5 с добавлением некоторых атрибутов: для привязки данных, ветвления, циклов и т. п.
Как и в React, здесь можно использовать собственные теги в качестве конструкторов компонент, но несколько иначе: сначала компонента регистрируется во Vue с некоторым именем -- и далее его можно использовать в шаблоне. Для сравнения: в React в качестве имён тегов используются просто имена js-переменных, видимые в текущем блоке.
Шаблоны во Vue, как и в React, компилируются, но в результате получаются отдельные объекты. Текст шаблонов и js-код компонент не смешиваются в единое целое.
Заодно Vue.js поддерживает и JSX, но, насколько можно судить, это одна из многочисленных дополнительных возможностей.
Механизм генерации фрагментов Web-страниц в elu.js (функция to_fill), отчасти напоминает Vue.js (сравнить, например, атрибуты data-on и v-show), но гораздо менее функционален: здесь
- нельзя доопределять теги (тем более для инициализации компонент);
- выражения не поддерживаются в принципе: интерполируются только заранее вычисленные значения именованных переменных.
data-on
предусмотрен его двойник data-off
, а также ряд атрибутов форматирования (data-digits
для чисел, data-dt
для времени), аналоги которых в проектах на React и Vue, вероятно, реализуются компонентами.
Шаблоны в elu.js (пока) не компилируются и быстродействие функции to_fill, мягко говоря, не на высоте. Впрочем, ощущается это только при отрисовке SELECT с тысячами OPTION.
В этом разделе, освещающем важную подтему предыдущего, пожалуй, стоит несколько поменять порядок рассмотрения.
Важнейшее свойство Vue.js -- превращать любой кусок данных, переданный для подстановки данных в шаблон, в объект наблюдения. И далее при изменении каждого его свойства перерисовывать на экране всё, что от этого свойства зависит.
То есть, если, скажем, объект data
, соответствующий учётной записи пользователя, содержит поле label
(ФИО), привязанное в шаблон дважды: в заголовок страницы и в поле ввода, то как только оператор отредактирует строку в поле, она тут же изменится и в заголовке. Без программирования: то есть и прослушиватель событий ввода и процедура обновления DOM-дерева реализованы в движке.
Разумеется, это работает и по отдельности:
- программное изменение
data.label
перерисовывает DOM; - редактирование поля ввода меняет
data.label
, доступное программному коду.
data.label
Vue подставляет getter / setter: в частности, поэтому магия работает только для полей, указанных изначально. Кроме того, разработчик компоненты может определять (отдельно от методов) вычислимые свойства, доступные в шаблонах только для чтения. То есть как getter без setter'а в javaBeans.
Во Vue.js привязка данных в шаблонах обозначается несколькими разными атрибутами (v-text, v-html, v-bind, v-model), элементом slot и вдобавок ещё тегами mustache для текстовых зон.
По контрасту с последней фразой: в React всего один, универсальный (и, соответственно, очевидный) способ вставки данных в шаблоны: фигурные скобки. В них можно писать любые js-выражения. В том числе содержащие JSX: так что шаблоны могут быть вложенными, как интерполируемые задние кавычки в ES6.
Но, понятное дело, так данные могут передаваться только в одну сторону: если написать <input value="{f + i + o}">
, то фарш невозможно провернуть назад. А React и не собирался этого делать и никому ничего подобного не обещал.
Он делает другое: при попытке повторной отрисовки компоненты он сравнивает объект данных с использованным ранее и если не видит изменений, то не трогает DOM. Этим "реактивность" в данном случае исчерпывается.
Тут, правда, в случае объектных компонент возникает ещё одна проблема: для установки нового состояния setState
каждый раз надо передавать объекты данных, полностью созданные заново -- иначе diff не сработает. Может показаться странным, что при таком подходе в API нет чего-то вроде clone из elu.js.
Разумеется, если бы дело ограничилось одной только функцией, это было бы слишком просто. Так дела не делаются. Вместо функции можно и нужно добавить минимум один фреймворк.
Стандартным дополнением для React предполагается отдельный движок хранения переменных в памяти (и их наблюдаемого изменения) Redux. Точнее, React и Redux входят в официальную докитрину fb о том, как надо делать UI Flux.
Интересный факт: несмотря на то, что у Vue.js наблюдаемость реализована изначально, в этом мире есть клон Redux: Vuex. В его документации даже переиспользованы иллюстрации с сайта Redux.
В elu.js можно обнаружить некое подобие React'овского варианта обновления фрагмента экрана по шаблону: функцию refill. Правда, на практике используется она редко. Гораздо чаще перерисовка производится либо средствами jQuery, либо других библиотек, работающих напрямую с DOM.
Здесь никогда не ставилась цель ни полностью контролировать DOM-дерево, ни реализовать двунаправленный поток данных без программирования.
Зато всюду, где возможно, привязка данных, да и обработчиков событий, производится простым декларативным, хотя и неявным образом: по атрибуту name
. Для полей ввода это имя компоненты данных, а для элементов BUTTON -- имя действия, в соответствии с которым выбирается функция $_DO [`${action}_${type}`]
.
И React, и Vue.js стремятся полностью контролировать содержимое страницы через периодическое обновление дерева элементов. Новые компоненты, по всей видимости, в основном создаются вложенными тегами в рамках родительских шаблонов, что геометрически естественно выглядит как вложенные прямоугольники. Однако время от времени неизбежно возникает необходимость перерисовать одну компоненту, когда что-то происходит в другой: в противоположном углу экрана, не материнской и не дочерней. Объектная парадигма наводит на мысль о том, что одна компонента должна вызвать метод другой, но как обеспечить доступ?
В React на этот счёт (как и на многие прочие) предусмотрено простое, очевидное и довольно скучное решение: найти общего предка вызывающей и вызываемой компоненты и спустить им через параметры пару setter / getter. Скучное -- поскольку передавать надо по всей последовательности вложений. Зато явно. Впрочем, для некоторых совсем глобальных вещей типа имени пользователя (то, что в elu.js -- переменная $_USER) имеется API Context: механизм "впрыска", аналогичный java CDI с заменой аннотаций на JSX-компоненты.
Vue.js, в своём духе, добавляет сюда малой механизации, или магии (как посмотреть): помимо своего CDI, здесь также доступны явные ссылки на детей $refs
и предков $root
/ $parent
(конечно же, категорически не рекомендуемые к использованию).
В elu.js, как упомянуто выше, компоненты вообще не являются объектами данных, а реализованы в виде наборов функций, при необходимости отовсюду доступных в составе глобальных переменных $_GET
, $_DRAW
и $_DO
. Вообще глобальные переменные здесь применяются повсеместно. Это, конечно, идёт явно вразрез с "хорошими практиками"... Зато весьма удобно. При заполнении шаблона объект данных сохраняется jQuery-функцией data () в контексте его корневого элемента. Набор данных, необходимый для показа страницы, чаще всего запоминается аналогичным образом в привязке к BODY. Кроме того, для одноразовой передачи данных асинхронному процессу мимо стека применяется пара $_SESSION.set / $_SESSION.delete.
Модные js-платформы в качестве своего важного ценного свойства указывают ту лёгкость, с которой позволяют разрабатывать одностраничные приложения. Вообще-то само понятие SPA естественным образом вытекает из того же процесса, который привёл к возникновению самих этих "фреймо-ворков": переноса логики с сервера на клиент. Когда 2-й ExtJS при загрузке страницы вращал шестерёночками по нескольку секунд -- всем стало понятно, что страницы эти не пооткрываешь с такой же скоростью, как на википедии. Тут просто не могло не возникнуть мысли грузить весь js сайта один раз в день, а дальше подкачивать только маленькие кусочки JSON и картинки по необходимости. И желательно заблокировать F5/Ctrl-R. То есть ориентация на SPA для js-платформы сегодня примерно такое же "преимущество", как способность автомобиля ездить по асфальтированной трассе.
Правда, если страница на сайте одна, возникает вопрос: один ли у неё адрес (URL)? Если один (например, /
), это крайне неудобно, поскольку рушит возможность ставить внешние ссылки на разные объекты в рамках одной сиситемы. Что, между прочим, представляет собой краеугольный камень принципа R.E.S.T. Итак, нужна одна HTML-страница, js-код которой обрабатывает текущее состояние location
и организует отображение нужного содержимого. А также замена старому доброму тегу <a>
, которая переключала бы location
, не вызывая перезагрузки всего, ссылками на что нашпигован <head>
.
В мирах React и Vue.js для решения этой задачи существуют отдельно стоящие наборы компонент: "маршрутизаторы" (React Router и Vue Router соответственно). Что существенного они дают по сравнению с использованием location
и history
, особенного современных, с history.replaceState
, автору пока не очевидно. Для React это может быть обусловлено унификацией кона приложений с React Native, но у Vue.js аналога нет.
Так или иначе, приложения на базе elu.js всегда были 100% SPA, а вся маршрутизация заключалась в разборе location.pathname
в глобальную переменную $_REQUEST
с последующим показом компоненты $_REQUEST.type
. Переписывание истории (пока) не освоено, но проблем с перезагрузкой js- и css- библиотек не возникает просто в силу управления кэшированием: в том числе при открытии в одном браузере многих вкладок с разными URL.
С другой стороны, в elu-приложениях нет аналога тега <a>
: вместо этого нужным элементам в процедурной части назначаются обработчики onClick, как правило, использующие функцию open_tab.
React и Vue.js похожи в том, что реализуют *ML-образные шаблоны с "живой" привязкой к данным, контролируя дерево DOM путём его постоянной перерисовки по иерархии шаблонов.
Такие инструменты, возможно, хороши при разработке большого Web-приложения, дизайн которого проектируется с нуля большой командой при наличии достаточных ресурсов: fb, AliPay и т. п. -- либо с использованием готвого конструктора, базирующегося именно на React или Vue.js (та или иная ветвь antd соответственно).
По многим рассмотренным вопросам там, где в React предусмотрено одно очевидное решение, Vue.js предполагает множество разнообразных опций. Правда, в React при прочих равных условиях, скорее всего, больше объём js-кода и выше риск перемешивания процедурных кусков с презентационными. Можно предположить, что React является естественным выбором для больших корпораций, где хватает штата и бюджета не только на тестирование, но и на вычитку исходных текстов, а Vue.js ближе одиночкам-самодельщикам.
Что же до elu.js, она была и остаётся лёгким средством, позволяющим разрабатывать UI на сотню-другую компонент в комплекте с jQuery и сторонней библиотекой форм/таблиц типа w2ui.