Глава 13. Расширение клиента Hyperview
В предыдущей главе мы создали полнофункциональную мобильную версию нашего приложения «Контакты». Помимо настройки URL-адреса точки входа, нам не нужно было трогать какой-либо код, который работает на мобильном устройстве. Мы полностью определили пользовательский интерфейс и логику нашего мобильного приложения во внутреннем коде, используя шаблоны Flask и HXML. Это возможно, поскольку стандартный клиент Hyperview поддерживает все основные функции мобильных приложений.
Но стандартный клиент Hyperview не может делать все «из коробки». Как разработчики приложений, мы хотим, чтобы приложения имели уникальные особенности, такие как настраиваемые пользовательские интерфейсы или глубокую интеграцию с возможностями платформы. Для удовлетворения этих потребностей клиент Hyperview был разработан с возможностью расширения за счет настраиваемых поведенческих действий и элементов пользовательского интерфейса. В этом разделе мы улучшим наше мобильное приложение, используя примеры того и другого.
Прежде чем углубиться, давайте кратко рассмотрим стек технологий, которые мы будем использовать. Клиент Hyperview написан на React Native, популярной кроссплатформенной платформе для создания мобильных приложений. Он использует тот же компонентный API, что и React. Это означает, что разработчики, знакомые с JavaScript и React, могут быстро освоить React Native. React Native имеет здоровую экосистему библиотек с открытым исходным кодом. Мы будем использовать эти библиотеки для создания собственных расширений для клиента Hyperview.
Добавление телефонных звонков и электронной почты
Начнем с самой очевидной функции, отсутствующей в нашем приложении «Контакты»: телефонных звонков. Мобильные устройства могут совершать телефонные звонки. Контакты в нашем приложении имеют номера телефонов. Разве наше приложение не должно поддерживать вызов этих телефонных номеров? И пока мы этим занимаемся, наше приложение также должно поддерживать отправку контактов по электронной почте.
В Интернете вызов телефонных номеров поддерживается с помощью tel:
схемы URI, а электронная почта поддерживается с помощью mailto:
схемы URI:
При нажатии предложить пользователю позвонить по указанному номеру телефона.
При нажатии откроется почтовый клиент с указанным адресом, указанным в поле
to:
.
Клиент Hyperview не поддерживает схемы URI tel:
и mailto:
. Но мы можем добавить эти возможности клиенту с помощью настраиваемых поведенческих действий. Помните, что поведение — это взаимодействия, определенные в HXML. Поведения имеют триггеры («нажать», «обновить») и действия («обновить», «поделиться»). Значения «действия» не ограничиваются набором, который входит в библиотеку Hyperview. Итак, давайте определим два новых действия: «открыть телефон» и «открыть электронную почту».
Определите псевдоним для пространства имен XML, используемого нашими новыми атрибутами.
При нажатии предложит пользователю позвонить по указанному номеру телефона.
При нажатии откройте почтовый клиент с указанным адресом, указанным в поле
to:
.
Обратите внимание, что мы определили фактический номер телефона и адрес электронной почты, используя отдельные атрибуты. В HTML схема и данные запихиваются в href
атрибут. Элементы HXML <behavior>
предоставляют больше возможностей для представления данных. Мы решили использовать атрибуты, но могли бы представить номер телефона или адрес электронной почты, используя дочерние элементы. Мы также используем пространство имен, чтобы избежать потенциальных конфликтов в будущем с другими клиентскими расширениями.
Пока все хорошо, но откуда клиент Hyperview знает, как интерпретировать open-phone
и open-email
и как ссылаться на атрибуты phone-number
и email-address
? Здесь нам, наконец, нужно написать немного JavaScript.
Сначала мы добавим стороннюю библиотеку ( react-native-communications
) в наше демонстрационное приложение. Эта библиотека предоставляет простой API, который взаимодействует с функциями уровня ОС для звонков и электронной почты.
Добавить зависимость от
react-native-communications
Перезапустите мобильное приложение
Далее мы создадим новый файл, phone.js
который будет реализовывать код, связанный с open-phone
действием:
Импортируйте нужную нам функцию из сторонней библиотеки.
Название действия.
Обратный вызов, который запускается при срабатывании действия.
Получите номер телефона из
<behavior>
элемента.Передайте номер телефона функции из сторонней библиотеки.
Пользовательские действия определяются как объект JavaScript с двумя ключами: action
и callback
. Вот как клиент Hyperview связывает настраиваемое действие в HXML с нашим настраиваемым кодом. Значение обратного вызова — это функция, которая принимает один параметр behaviorElement
. Этот параметр представляет собой XML-DOM-представление элемента <behavior>
, вызвавшего действие. Это означает, что мы можем вызывать его методы, например getAttribute
, или получать доступ к его атрибутам, например childNodes
. В этом случае мы используем getAttributeNS
для чтения номер телефона из phone-number
атрибута <behavior>
элемента. Если номер телефона определен в элементе, мы можем вызвать phonecall()
функцию, предоставляемую библиотекой react-native-communications
.
Прежде чем мы сможем использовать наше настраиваемое действие, нужно сделать еще одну вещь: зарегистрировать действие в клиенте Hyperview. Клиент Hyperview представлен как компонент React Native под названием Hyperview
. Этот компонент принимает свойство с именем behaviors
, которое представляет собой массив объектов настраиваемых действий, таких как наше действие «открыть телефон». Давайте передадим нашу реализацию «открытого телефона» компоненту Hyperview
нашего демонстрационного приложения.
Импортируйте действие открытия телефона.
Создайте массив настраиваемых действий.
Передайте пользовательские действия компоненту
Hyperview
в виде свойства с именемbehaviors
.
Под капотом Hyperview
компонент отвечает за преобразование HXML в элементы мобильного пользовательского интерфейса. Он также обрабатывает запуск поведенческих действий на основе взаимодействия с пользователем.
Передав действие «открыть телефон» в Hyperview, мы теперь можем использовать его в качестве значения атрибута action
элементов <behavior>
. Собственно, давайте сделаем это сейчас, обновив show.xml
шаблон в нашем приложении Flask:
Добавьте в раздел номера телефона поведение, которое срабатывает при нажатии.
Запустите новое действие «открыть телефон».
Установите атрибут, ожидаемый действием «открыть телефон».
Та же идея, но с другим действием («открыть письмо»).
Мы пропустим реализацию второго дополнительного действия — «открыть электронное письмо». Как вы можете догадаться, это действие откроет композитор электронной почты системного уровня, позволяющий пользователю отправить электронное письмо своему контакту. Реализация «открытой электронной почты» практически идентична реализации «открытого телефона». Библиотека react-native-communications
предоставляет функцию с именем email()
, поэтому мы просто обертываем ее и передаем ей аргументы таким же образом.
Теперь у нас есть полный пример расширения клиента с помощью настраиваемых поведенческих действий. Мы выбрали новое имя для наших действий («открыть телефон» и «открыть электронную почту») и сопоставили эти имена с функциями. Функции принимают <behavior>
элементы и могут запускать любой произвольный код React Native. Мы обернули существующую стороннюю библиотеку и прочитали атрибуты, установленные для элемента, <behavior>
для передачи данных в библиотеку. После перезапуска нашего демонстрационного приложения у нашего клиента появляются новые возможности, которые мы можем немедленно использовать, ссылаясь на действия из наших шаблонов HXML.
Добавление сообщений
Действия по телефону и электронной почте, добавленные в предыдущем разделе, являются примерами «системных действий». Действия системы запускают некоторый пользовательский интерфейс или возможности, предоставляемые ОС устройства. Но пользовательские действия не ограничиваются взаимодействием с API уровня ОС. Помните, что обратные вызовы, реализующие действия, могут запускать произвольный код, включая код, отображающий наши собственные элементы пользовательского интерфейса. Следующий пример специального действия сделает именно это: отобразит элемент пользовательского интерфейса пользовательского сообщения с подтверждением.
Если вы помните, наше веб-приложение «Контакты» отображает сообщения об успешных действиях, таких как удаление или создание контакта. Эти сообщения генерируются в бэкэнде Flask с помощью flash()
функции, вызываемой из представлений. Затем базовый layout.html
шаблон отображает сообщения на конечной веб-странице.
Наше приложение Flask по-прежнему включает вызовы flash()
, но приложение Hyperview не получает доступа к отображаемому сообщению для отображения пользователю. Давайте добавим эту поддержку сейчас.
Мы могли бы просто показывать сообщения, используя метод, аналогичный веб-приложению: прокручивать сообщения и отображать некоторые <text>
элементы в формате layout.xml
. У этого подхода есть серьезный недостаток: отображаемые сообщения будут привязаны к определенному экрану. Если этот экран был скрыт действием навигации, сообщение также будет скрыто. Чего мы действительно хотим, так это чтобы наш пользовательский интерфейс сообщений отображался «над» всеми экранами в стеке навигации. Таким образом, сообщение останется видимым (исчезнет через несколько секунд), даже если стопка экранов изменится ниже. Чтобы отобразить некоторый пользовательский интерфейс за пределами <screen>
элементов, нам понадобится расширить клиент Hyperview новым настраиваемым действием show-message
. Это еще одна возможность использовать библиотеку с открытым исходным кодом react-native-root-toast
. Давайте добавим эту библиотеку в наше демонстрационное приложение.
Добавить зависимость от
react-native-root-toast
Перезапустите мобильное приложение
Теперь мы можем написать код для реализации пользовательского интерфейса сообщений в качестве настраиваемого действия.
Импортируйте
Toast
API.Название действия.
Обратный вызов, который запускается при срабатывании действия.
Передайте сообщение в библиотеку тостов.
Этот код очень похож на реализацию open-phone
. Оба обратных вызова следуют одинаковой схеме: считывают атрибуты пространства имен из <behavior>
элемента и передают эти значения в стороннюю библиотеку. Для простоты мы жестко запрограммировали параметры, позволяющие отображать сообщение в верхней части экрана, исчезающее через 2 секунды. Но react-native-root-toast
предоставляет множество вариантов позиционирования, времени анимации, цветов и многого другого. Мы могли бы указать эти параметры, используя дополнительные атрибуты, behaviorElement
чтобы сделать действие более настраиваемым. Для наших целей мы просто будем придерживаться простой реализации.
Теперь нам нужно зарегистрировать наше пользовательское действие в <Hyperview>
компоненте, передав его в behaviors
свойство.
Импортируйте
show-message
действие.Передайте действие компоненту
Hyperview
в виде свойства с именемbehaviors
.
Все, что осталось сделать, это запустить show-message
действие из нашего HXML. Существует три действия пользователя, которые приводят к отображению сообщения:
Создание нового контакта
Обновление существующего контакта
Удаление контакта
Первые два действия реализованы в нашем приложении с использованием одного и того же шаблона HXML form_fields.xml
: . После успешного создания или обновления контакта этот шаблон перезагрузит экран и вызовет событие, используя поведение, которое срабатывает при «загрузке». Действие удаления также использует поведение, которое срабатывает при «загрузке», определенном в deleted.xml
шаблоне. Поэтому оба form_fields.xml
варианта deleted.xml
необходимо изменить, чтобы они также отображали сообщения при загрузке. Поскольку фактическое поведение в обоих шаблонах будет одинаковым, давайте создадим общий шаблон для повторного использования HXML.
Определите поведение для каждого отображаемого сообщения.
Запустите это поведение, как только элемент загрузится.
Запустите новое действие «показать сообщение».
Действие «show-message» отобразит мигающее сообщение в пользовательском интерфейсе.
Как и в layout.html
веб-приложении, мы просматриваем все отображаемые сообщения и отображаем разметку для каждого сообщения. Однако в веб-приложении сообщение было непосредственно отображено на веб-странице. В приложении Hyperview каждое сообщение отображается с использованием поведения, которое запускает наш пользовательский интерфейс. Теперь нам просто нужно включить этот шаблон в form_fields.xml
:
Показывать сообщения сразу после загрузки экрана.
И мы можем сделать то же самое в deleted.xml
:
Показывать сообщения сразу после загрузки экрана.
В обоих случаях form_fields.xml
и deleted.xml
при «загрузке» запускается несколько вариантов поведения. В deleted.xml
мы сразу же возвращаемся к предыдущему экрану. В режиме form_fields.xml
мы немедленно перезагружаем текущий экран, чтобы отобразить сведения о контакте. Если бы мы отображали элементы пользовательского интерфейса сообщений непосредственно на экране, пользователь едва ли бы их увидел, прежде чем экран исчезнет или перезагрузится. Используя настраиваемое действие, пользовательский интерфейс сообщений остается видимым, даже если экраны под ним меняются.
Жест смахивания по контактам
Чтобы добавить возможности связи и пользовательский интерфейс сообщений, мы расширили клиент настраиваемыми действиями поведения. Но клиент Hyperview также можно расширить с помощью пользовательских компонентов пользовательского интерфейса, отображаемых на экране. Пользовательские компоненты реализуются как компоненты React Native. Это означает, что все, что возможно в React Native, можно сделать и в Hyperview! Пользовательские компоненты открывают безграничные возможности для создания многофункциональных мобильных приложений с архитектурой Hypermedia.
Чтобы проиллюстрировать эти возможности, мы расширим клиент Hyperview в нашем мобильном приложении, добавив компонент «перелистываемой строки». Как это работает? Компонент «перелистываемой строки» поддерживает жест горизонтального пролистывания. Когда пользователь проводит по этому компоненту справа налево, он перемещается, открывая ряд кнопок действий. Каждая кнопка действия сможет запускать стандартное поведение Hyperview при нажатии. Мы будем использовать этот пользовательский компонент на экране списка контактов. Каждый элемент контакта будет представлять собой «перелистываемую строку», а действия обеспечат быстрый доступ к действиям по редактированию и удалению контакта.
Проектирование компонента
Вместо того, чтобы реализовывать жест смахивания с нуля, мы снова будем использовать стороннюю библиотеку с открытым исходным кодом: react-native-swipeable
.
Добавьте зависимость от
react-native-swipeable
.Перезапустите мобильное приложение.
Эта библиотека предоставляет компонент React Native под названием Swipeable
. Он может отображать любые компоненты React Native в качестве основного содержимого (той части, которую можно перелистывать). Он также использует массив компонентов React Native в качестве реквизита для отображения в виде кнопок действий.
При разработке пользовательского компонента мы предпочитаем определять HXML компонента перед написанием кода. Таким образом, мы можем быть уверены, что разметка будет выразительной, но лаконичной и будет работать с базовой библиотекой.
Для прокручиваемой строки нам нужен способ представления всего компонента, основного содержимого и одной из многих кнопок.
Родительский элемент, инкапсулирующий всю прокручиваемую строку, с настраиваемым пространством имен.
Основное содержимое прокручиваемой строки может содержать любой HXML.
Первая кнопка, которая появляется при пролистывании, может содержать любой HXML.
Вторая кнопка, которая появляется при пролистывании, может содержать любой HXML.
Эта структура четко отделяет основной контент от кнопок. Он также поддерживает одну, две или более кнопок. Кнопки появляются в порядке определения, что позволяет легко менять порядок.
Этот дизайн охватывает все, что нам нужно для реализации пролистываемой строки для нашего списка контактов. Но он также достаточно универсален, чтобы его можно было использовать повторно. Предыдущая разметка не содержит ничего конкретного относительно имени контакта, его редактирования или удаления. Если позже мы добавим в наше приложение еще один экран списка, мы сможем использовать этот компонент, чтобы сделать элементы в этом списке пролистываемыми.
Реализация компонента
Теперь, когда мы знаем структуру HXML нашего пользовательского компонента, мы можем написать код для его реализации. Как выглядит этот код? Компоненты Hyperview написаны как компоненты React Native. Эти компоненты React Native сопоставлены с уникальным пространством имен XML и именем тега. Когда клиент Hyperview встречает это пространство имен и имя тега в HXML, он делегирует рендеринг элемента HXML соответствующему компоненту React Native. В рамках делегирования клиент Hyperview передает несколько реквизитов компоненту React Native:
element
: элемент XML DOM, который сопоставляется с компонентом React Native.stylesheets
: стили, определенные в файле<screen>
.onUpdate
: функция, вызываемая, когда компонент запускает поведение.option
: разные настройки, используемые клиентом Hyperview.
Наш компонент строки с возможностью перелистывания представляет собой контейнер со слотами для отображения произвольного основного контента и кнопок. Это означает, что для рендеринга этих частей пользовательского интерфейса необходимо делегировать обратно клиенту Hyperview. Это делается с помощью общедоступной функции, предоставляемой клиентом Hyperview, Hyperview.renderChildren()
.
Теперь, когда мы знаем, как реализованы пользовательские компоненты Hyperview, давайте напишем код для нашей перелистываемой строки.
Компонент React Native на основе классов.
Сопоставьте этот компонент с заданным пространством имен HXML.
Сопоставьте этот компонент с данным именем тега HXML.
Функция, которая возвращает массив компонентов React Native для каждого
<button>
элемента.Делегируйте клиенту Hyperview обработку каждой кнопки.
Передайте кнопки и основной контент в стороннюю библиотеку.
Делегируйте клиенту Hyperview рендеринг основного контента.
Класс SwipeableRow
реализует компонент React Native. В верхней части класса мы устанавливаем статическое namespaceURI
свойство и localName
свойство. Эти свойства сопоставляют компонент React Native с уникальной парой пространства имен и имен тегов в HXML. Именно так клиент Hyperview знает, кому следует делегировать полномочия SwipeableRow
при обнаружении пользовательских элементов в HXML. Внизу класса вы увидите render()
метод. render()
вызывается React Native для возврата визуализированного компонента. Поскольку React Native построен по принципу композиции, render()
обычно возвращает композицию других компонентов React Native. В этом случае мы возвращаем Swipeable
компонент (предоставленныйreact-native-swipeable
библиотека), составленная с использованием компонентов React Native для кнопок и основного контента. Компоненты React Native для кнопок и основного контента создаются с использованием аналогичного процесса:
Найдите конкретные дочерние элементы (
<button>
или<main>
).Превратите эти элементы в компоненты React Native, используя
Hyperview.renderChildren()
.Установите компоненты как дочерние элементы или реквизиты
Swipeable
.
Этот код может быть трудным для понимания, если вы никогда не работали с React или React Native. Это нормально. Важный вывод: мы можем написать код для перевода произвольного HXML в компоненты React Native. Структура HXML (как атрибуты, так и элементы) может использоваться для представления нескольких аспектов пользовательского интерфейса (в данном случае — кнопок и основного контента). Наконец, код может делегировать рендеринг дочерних компонентов обратно клиенту Hyperview.
Результат: этот компонент строки с возможностью перелистывания является полностью универсальным. Фактическая структура, стиль и взаимодействие основного контента и кнопок могут быть определены в HXML. Создание универсального компонента означает, что мы можем повторно использовать его на нескольких экранах для разных целей. Если в будущем мы добавим больше пользовательских компонентов или новых поведенческих действий, они будут работать с нашей реализацией перелистываемых строк.
Последнее, что нужно сделать, это зарегистрировать этот новый компонент в клиенте Hyperview. Этот процесс аналогичен регистрации пользовательских действий. Пользовательские компоненты передаются как отдельный components
реквизит для Hyperview
компонента.
Импортируйте
SwipeableRow
компонент.Создайте массив пользовательских компонентов.
Передайте пользовательский компонент компоненту
Hyperview
как свойство с именемcomponents
.
Теперь мы готовы обновить наши шаблоны HXML, чтобы использовать новый компонент перелистываемых строк.
Использование компонента
В настоящее время HXML для элемента контакта в списке состоит из элемента <behavior>
и <text>
:
Благодаря нашему компоненту перелистываемых строк эта разметка станет «основным» пользовательским интерфейсом. Итак, давайте начнем с добавления <row>
и <main>
в качестве родительских элементов.
Добавлен
<swipe:row>
родительский элемент с псевдонимом пространства имен дляswipe
.Добавлен
<swipe:main>
элемент для определения основного контента.Обернул существующие элементы
<behavior>
и<text>
в файл<view>
.
Раньше contact-item
стиль был задан для <item>
элемента. Это имело смысл, когда <item>
элемент был контейнером для основного содержимого элемента списка. Теперь, когда основной контент является дочерним элементом <swipe:main>
, нам нужно добавить новый элемент <view>
, в котором мы будем применять стили.
Если мы перезагрузим нашу серверную часть и мобильное приложение, вы пока не увидите никаких изменений на экране списка контактов. Без определенных кнопок действий при пролистывании строки ничего не будет отображаться. Давайте добавим две кнопки в пролистываемую строку.
Добавлено
<swipe:button>
для действия редактирования.Добавлено
<swipe:button>
для действия удаления.
Теперь, если мы воспользуемся нашим мобильным приложением, мы сможем увидеть прокручиваемую строку в действии! Когда вы проводите по элементу контакта, появляются кнопки «Изменить» и «Удалить». Но они пока ничего не делают. Нам нужно добавить к этим кнопкам некоторые варианты поведения. Кнопка «Редактировать» проста: нажатие на нее должно открыть экран сведений о контакте в режиме редактирования.
При нажатии открывается новый экран с пользовательским интерфейсом редактирования контакта.
Кнопка «Удалить» немного сложнее. Нет экрана, который можно было бы открыть для удаления, так что же должно произойти, когда пользователь нажмет эту кнопку? Возможно, мы используем то же взаимодействие, что и кнопка «Удалить» на экране «Редактировать контакт». В результате этого взаимодействия открывается системный диалог, в котором пользователю предлагается подтвердить удаление. Если пользователь подтвердит, клиент Hyperview отправляет запрос POST
и /contacts/<contact_id>/delete
добавляет ответ на экран. Ответ немедленно запускает несколько действий: перезагрузка списка контактов и отображение сообщения. Это взаимодействие будет работать и для нашей кнопки действия:
При нажатии открывается системное диалоговое окно с просьбой подтвердить действие.
В случае подтверждения сделайте запрос POST к конечной точке удаления и добавьте ответ к родительскому файлу
<item>
.
Теперь, когда мы нажимаем «Удалить», мы получаем диалоговое окно подтверждения, как и ожидалось. После нажатия кнопки подтверждения ответ серверной части запускает поведение, которое отображает сообщение с подтверждением и перезагружает список контактов. Элемент удаленного контакта исчезнет из списка.
Обратите внимание, что кнопки действий могут поддерживать любой тип поведенческого действия, от push
до alert
. Если бы мы захотели, мы могли бы сделать так, чтобы кнопки действий запускали наши специальные действия, например open-phone
и open-email
. Пользовательские компоненты и действия можно свободно смешивать со стандартными компонентами и действиями, которые входят в стандартную комплектацию Hyperview. Благодаря этому расширения клиента Hyperview кажутся первоклассными функциями.
На самом деле, мы откроем вам секрет. В клиенте Hyperview стандартные компоненты и действия реализованы так же, как и пользовательские компоненты и действия! Код рендеринга не <view>
отличается от кода <swipe:row>
. Код поведения не alert
отличается от кода open-phone
. Оба они реализованы с использованием одних и тех же методов, описанных в этом разделе. Стандартные компоненты и действия — это именно те, которые необходимы всем мобильным приложениям. Но они являются лишь отправной точкой.
Большинству мобильных приложений потребуются некоторые расширения клиента Hyperview, чтобы обеспечить удобство работы с пользователем. Расширения превращают клиент из обычного «клиента Hyperview» в клиент, специально созданный для вашего приложения. И что немаловажно, эта эволюция сохраняет Hypermedia, серверную архитектуру и все ее преимущества.
Мобильные гипермедийные приложения
На этом наша сборка мобильного Contact.app завершена. Отойдите от деталей кода и рассмотрите более широкую картину:
Основная логика приложения находится на сервере.
Шаблоны, отображаемые на сервере, используются как в веб-приложениях, так и в мобильных приложениях.
Настройка платформы осуществляется с помощью сценариев в Интернете и настройки клиента на мобильных устройствах.
Архитектура приложений, управляемых гипермедиа, допускала значительное повторное использование кода и управляемый технологический стек. Текущие обновления и обслуживание приложений для Интернета и мобильных устройств могут выполняться одновременно.
Да, есть история с приложениями, управляемыми гипермедиа, на мобильных устройствах.
Заметки о Hypermedia: достаточно хороший UX и острова интерактивности
Проблема, с которой сталкиваются многие разработчики SPA и мобильных приложений при переходе к подходу HDA, заключается в том, что они смотрят на свое текущее приложение и представляют себе его реализацию именно с использованием гипермедиа. Несмотря на то, что htmx и Hyperview значительно улучшают взаимодействие с пользователем, доступное благодаря подходу, основанному на гипермедиа, все же бывают случаи, когда получить конкретный пользовательский интерфейс будет непросто.
Как мы видели во второй главе, Рой Филдинг отметил этот компромисс по отношению к сетевой архитектуре Интернета RESTful, где «информация передается в стандартизированной форме, а не в форме, специфичной для нужд приложения».
Принятие немного менее эффективного и интерактивного решения для конкретного UX может существенно сэкономить вам время при создании приложений.
Не позволяйте лучшему быть врагом хорошего. Многие преимущества можно получить, приняв в некоторых случаях чуть менее сложный пользовательский интерфейс, а такие инструменты, как htmx и Hyperview, делают этот компромисс гораздо более приемлемым при правильном использовании.
Last updated