Skip to content

React, Vue.js и elu.js

do- edited this page Jan 11, 2021 · 50 revisions

Table of Contents

Введение

Текст этой статьи написан по итогам 10-дневного ознакомления автора библиотеки elu.js с React и Vue.js на примере Ant Design Pro и Ant Design Pro of Vue соответственно.

Автор никоим образом не претендует ни на широту охвата, ни на экспертизу в области современных информационных технологий.

Однако настоящий обзор может пригодиться некоторым разработчкам Web-интерфейсов -- хотя бы в качестве стартового набора ссылок.

О чём здесь (не) идёт речь

В основном здесь рассматриваются базовые javaScript-библиотеки для разработки Web-интерфейсов, где

  • вообще существенная часть логики реализуется на клиенте;
  • в частности, отдельные области экрана общаются с сервером и обновляются асинхронно и независимо друг от друга.
В описанной ситуации естественным образом возникает необходимость вместо "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

Классика

Первые версии 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") выглядит на первый взгляд не вполне очевидно. Но ко всему можно привыкнуть. Наверное.

Компоненты Vue.js

По состоянию на сегодня, 10.01.2021, рабочая версия Vue.js -- 2.х. В ней компоненты являются "объектами" в традиционном для js смысле: результатами new Vue ({...}), то есть вызова общего конструктора с широчайшим набором разнообразных опций.

Во Vue.js 3.х введено новшество: Composition API -- формально во многом повторяющее React Hooks. Однако здесь, по всей видимости, речь не идёт о столь же масштабной смене парадигмы. Просто добавляется очередная возможность писать более компакный код (коих во Vue уже изрядное число).

Компоненты elu.js

В elu.js компонента, или блок -- это побочный результат вызова функции show_block (устаревший вариант: use.block), который нигде впоследствии не существует и не может использоваться как переменная. Тип компоненты -- это лишь строка, фигурирующая в именах нескольких файлов и функций, связанных чисто тематически.

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

Механизм шаблонов

React: JSX

Исходные тексты компонент React (в основном) пишутся не на чистом js, а с расширением JSX. На первый взгляд это смотрится как javaScript вперемежку с HTML. На второй -- становится заметно, что это, по крайней мере, XHTML. На третий -- XML с некоторыми знакомыми тегами. И, наконец, переменные в фигурных скобках наводят на мысль о том, что мы имеем дело с клиентским аналогом JSF.

На самом деле каждый *ML-подобный тег прекомпилируется в js-код создания соответствующей компоненты, где значения атрибутов и дочерних объектов передаются в качестве параметров конструктору.

Vue.js: почти HTML

Vue.js реализует свой язык разметки, который довольно близок к стандартному HTML5 с добавлением некоторых атрибутов: для привязки данных, ветвления, циклов и т. п.

Как и в React, здесь можно использовать собственные теги в качестве конструкторов компонент, но несколько иначе: сначала компонента регистрируется во Vue с некоторым именем -- и далее его можно использовать в шаблоне. Для сравнения: в React в качестве имён тегов используются просто имена js-переменных, видимые в текущем блоке.

Шаблоны во Vue, как и в React, компилируются, но в результате получаются отдельные объекты. Текст шаблонов и js-код компонент не смешиваются в единое целое.

Заодно Vue.js поддерживает и JSX, но, насколько можно судить, это одна из многочисленных дополнительных возможностей.

elu.js: HTML как таковой

Механизм генерации фрагментов 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: система наблюдения

Важнейшее свойство 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: дёргай сам

По контрасту с последней фразой: в 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: третий путь

В 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.

SPA и навигация

Модные 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.

Clone this wiki locally