Альтернативное название: «Боже, что я наделал?»

Для тех из вас, кто не знаком с технологиями, упомянутыми в названии, краткое введение:

OpenUI5 - это версия с открытым исходным кодом новой инфраструктуры пользовательского интерфейса SAP SAPUI5. Это комплексное решение для предприятий, включающее батареи, UI-фреймворк, который включает в себя полный MVC, маршрутизацию, элементы управления UI, поддержку i18n и т. Д.
UI5 имеет довольно строгое разделение представлений и контроллеров. Каждое представление определяет структуру, используемые элементы управления, привязки данных и т. Д. И может объявлять контроллер, который следует использовать. Контроллеры следует использовать только для реагирования на события, запускаемые представлением, и обновления моделей, которые через привязку данных сигнализируют представлению об обновлении.

JSX (не путать с этим JSX), с другой стороны, можно назвать расширением синтаксиса JavaScript, введенным (насколько мне известно) Facebook в их структуре React. Сам React во многом отличается от других UI-фреймворков. В основном нет различий между представлениями и контроллерами. Есть просто Компоненты, и это (теоретически) чистые функции. Они получают входные данные (так называемые реквизиты) и возвращают выходные данные (получившуюся модель DOM).

В UI5 создание элементов управления в контроллере кажется довольно многословным и трудным для чтения:

(Да, это можно сделать лучше, импортировав необходимые классы в локальное пространство имен, но мне еще предстоит найти «хорошее» решение)

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

Ага, верно. Здесь мы смешиваем HTML и JS. Поначалу это может показаться пугающим, но я обещаю, что это будет довольно круто, когда вы к этому привыкнете.

Когда я прошел свои первые шаги с React и начал любить JSX, я сразу пропустил его в UI5, когда мне приходилось создавать элемент управления в коде контроллера.
Я мечтал о том, как это может выглядеть круто, насколько ясно это может быть .

React использует Babel для преобразования кода JSX в собственный JavaScript. Давайте попробуем. Есть babel-plugin-transform-jsx, который отлично решает эту проблему. JSX на входе, JS на выходе.

Чтобы использовать его, вам необходимо установить babel, babel-cli и плагин:

npm install babel babel-cli babel-plugin-transform-jsx

Кроме того, я бы посоветовал добавить файл .babelrc для настройки:

{
  "plugins": [ "transform-jsx"]
}

Это просто файл JSON, содержащий конфигурацию для babel.

Теперь вы можете просто транспилировать исходный код JSX:

babel source.js -d dist/

Учитывая следующий ввод (я только что добавил необходимый шаблон)

получаем следующий вывод

Wohoo. Это выглядит устрашающе. Но ничего страшного, это скомпилированный код. Вы когда-нибудь смотрели, что производит gcc?

Настоящая проблема в том, что это не работает. Он что-то делает, но не то, что мы планировали. Он просто переводит XML в объекты JS, представляющие те же значения. Нам как-то нужно создать элементы управления UI5 из этих объектов.

Для этого есть два варианта:

а) сгенерировать код, максимально приближенный к первому примеру. Вызов конструкторов напрямую, передавая конструктору содержимое attributes и children.

б) добавить код, который получает эти объекты во время выполнения и создает для нас элементы управления.

Хотя (а) даст более читаемый результат и лучшую производительность, существует проблема, которая делает (б) лучшим вариантом для меня.

Чтобы разобраться в проблеме, нам необходимо ввести понятие агрегаты. В UI5 агрегация - это свойство компонента, которое может содержать список других компонентов. Список содержит элементы, панель или SimpleForm может содержать содержимое, таблица содержит столбцы. Компонент может иметь несколько агрегатов и может (или не может) иметь единственное агрегирование по умолчанию. Для List - его элементы, для Panel - его содержимое. Таблица содержит столбцы и элементы, по умолчанию - элементы.

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

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

Код для этого может выглядеть примерно так:

Это довольно просто: мы ожидаем, что объект (здесь называется definition) будет содержать набор атрибутов, elementName, который является функцией или строкой, и, возможно, список дочерних элементов. Если elementName является функцией, мы ожидаем, что это будет класс UI5, и проверяем его метаданные на предмет агрегирования по умолчанию.

Плагин transform-jsx позволяет нам указать модуль, который при необходимости извлекается в пространствах имен и возвращает эту функцию ИЛИ, чтобы указать функцию, которая должна использоваться для создания элементов. Поскольку в настоящее время babel не может справиться с дисперсией UI5 для AMD, нам нужно самостоятельно обработать загрузку этого кода.

Давайте создадим файл createElement.js рядом с нашим source.js и импортируем его:

В source.js мы просто изменяем первые три строки:

sap.ui.define( 
  ["sap/ui/core/Controller", "sap/m/Label", "sap/m/Input", "sap/ui/layout/form/SimpleForm", "./createElement.js"], 
  function(Controller, Label, Input, SimpleForm, createElement) {

Теперь, когда createElement известен в нашей области видимости, мы можем изменить babelrc, чтобы сообщить transform-jsx о необходимости его использования.

{ 
  "plugins": [  
    [ "transform-jsx", { function: "createElement", useVariables: true} ] 
  ] 
}

Часть useVariables заставляет transform-jsx использовать имена элементов, которые начинаются с заглавной буквы и являются допустимыми идентификаторами в качестве идентификаторов. Таким образом, при переводе <List /> мы получим { elementName: List, ... } вместо { elementName: "List", ... }, что означает, что фактический конструктор для списка будет содержаться в определении объекта, а не только в имени.

Теперь source.js компилируется примерно так:

И это действительно могло сработать.

Я создал крошечный репозиторий на GitHub, который включает рабочий пример этого и некоторые дополнительные функции (кто-то сказал, что функции стрелок ES6?).



Так насколько это полезно?

Это зависит от вашего рабочего процесса. Если вы делаете всю разработку UI5 в SAP WebIDE, вы просто не сможете этого сделать. Если вы просто не хотите иметь дело с циклом change-compile-test, возможно, вам не стоит рассматривать преимущества дополнительных шагов. Хотя это можно было бы уменьшить с помощью чего-то вроде babel --watch.

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

Следующий шаг: виртуальный дом для RenderManager? ;)