HTMX против стандартного SPA: когда гипермедиа превосходит фреймворк JavaScript
Как атрибуты htmx заменяют рендеринг на стороне клиента, что на самом деле означают размеры пакетов, и сбалансированный обзор того, в чём преимущества гипермедийных приложений, а в чём — React.
Многие команды выбирают React для каждого нового проекта, включая панели администрирования и инструменты CRUD, которые в основном представляют собой формы и таблицы. htmx утверждает, что значительная часть таких приложений никогда не нуждалась в фреймворке с клиентской стороны: сервер может отправлять HTML, а несколько атрибутов позволяют любому элементу загружать и заменять его фрагменты. В этом руководстве объясняется, как работает htmx, сравниваются его требования к ресурсам с требованиями React, а также указываются как настоящие преимущества, так и ограничения, которые сторонники htmx часто игнорируют, чтобы вы могли осознанно выбирать архитектуру, а не по привычке.
В нем предполагается, что вы уже создавали веб-приложения и большую часть времени работали с React или похожим фреймворком.
Как SPA стал стандартом для всего
Примерно в 2013 году индустрия изменила свою основную модель. Вместо того чтобы сервера возвращали HTML-страницы, они начали возвращать JSON, а JavaScript в браузере преобразовывал этот JSON в интерфейс.
Для некоторых продуктов одностраничное приложение стало настоящим шагом вперед. Gmail, Figma и Google Maps хранят большое количество данных в браузере, и их взаимодействие должно казаться мгновенным.
Проблема в том, что эта модель распространилась на всё. Внутренний панель управления, который отображает строки и позволяет их редактировать, в итоге получил такую же архитектуру, как и инструменты для дизайна. Маркетинговый сайт с одной формой контакта приобрёл инструменты для объединения кода, маршрутизатор, библиотеку для управления состоянием и этап гидратации.
У такого стандарта есть конкретные издержки:
- Два набора кода, часто на двух языках
- Модель данных, определённая с обеих сторон
- Инструменты для сборки, которые необходимо поддерживать и обновлять
Основная идея htmx заключается в том, что многие приложения несут эти затраты, не получая взамен ничего нужного.
Что такое htmx
htmx — это небольшая библиотека на JavaScript, которая расширяет HTML новыми атрибутами. Благодаря им любой элемент может отправить HTTP-запрос и разместить полученный ответ где-либо на странице. По сути, в этом и заключается вся библиотека.
Примером может служить поле для поиска в режиме реального времени. В нем указывается, к какому URL следует обратиться, какое событие запускает запрос и куда должен попасть результат:
<input type="text"
name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results">
<div id="results"></div>
Читайте атрибуты как предложение: когда клавиша отпущена и значение действительно изменилось, подождите 300 мс, отправьте запрос GET по адресу /search и поместите полученный результат в элемент #results. Модификатор delay:300ms снижает частоту отправки запросов, так что быстрое ввод будет приводить к отправке запроса не по одной клавише.
Сервер не возвращает JSON. Он возвращает готовый фрагмент HTML:
<ul>
<li>First result</li>
<li>Second result</li>
</ul>
htmx вставляет этот фрагмент в целевой элемент. Не происходит парсинга JSON, нет шаблонов на стороне клиента, и состояние клиента не может отклоняться от значения на сервере.
Атрибуты, которые вы будете использовать чаще всего
hx-get,hx-post,hx-put,hx-delete— выбирают метод HTTP и URL.
hx-trigger определяет событие, при котором отправляется запрос, например click, keyup, load, every 2s или revealed (когда элемент появляется в области отображения).hx-target выбирает элемент, который получает ответ.hx-swap управляет способом вставки ответа: innerHTML, outerHTML, beforeend или delete.hx-indicator указывает на элемент, который отображается во время отправки запроса.hx-confirm просит пользователя подтвердить действие перед отправкой.Эти элементы хорошо сочетаются друг с другом. В следующем фрагменте кода удаляется строка таблицы: кнопка отправляет запрос DELETE, нацеливается на ближайший контейнер tr, заменяет всю эту строку на (обычно пустой) ответ и сначала запрашивает подтверждение. Модификатор swap:1s откладывает замену на секунду, что позволяет использовать эффект CSS-перехода для постепенного исчезновения строки и создает впечатление оперативной реакции системы:
<tr>
<td>Widget</td>
<td>
<button hx-delete="/items/42"
hx-target="closest tr"
hx-swap="outerHTML swap:1s"
hx-confirm="Delete this item?">
Delete
</button>
</td>
</tr>
Обратите внимание на отсутствующее: нет отдельного файла JavaScript, нет этапа сборки и нет состояния на стороне клиента. Сервер по-прежнему отвечает за удаление элемента и определение того, чем должна стать строка.
Две архитектуры рядом
Различия становятся более очевидными при рассмотрении процесса выполнения, чем при сравнении инструментов.
- Модель SPA: сервер отправляет JSON, клиентский код отображает его и управляет состоянием; пользователь вносит изменения, клиент обновляет своё состояние, перерисовывает интерфейс и возвращает JSON обратно.
- Модель гипермедиа: сервер отправляет HTML, браузер его отображает; пользователь вносит изменения, сервер отвечает новым HTML, и браузер заменяет старый контент на новый.
В модели гипермедиа существует ровно один источник истины — сервер. Это устраняет целый класс ошибок, при которых клиент считает что-то иное, чем указано в базе данных, таких как устаревшие кэши или оптимистичные обновления, которые так и не были синхронизированы.
Карсон Гросс, создатель htmx, представляет его как возвращение к тому, чем изначально должен был быть REST. Подход REST Роя Филдинга включает ограничение HATEOAS (Hypermedia As The Engine Of Application State): сервер отправляет данные, содержащие информацию о действиях, которые можно выполнить далее. Типичный JSON API этого не делает; клиенту необходимо заранее знать, какие конечные точки существуют. HTML делает это встроенно, поскольку ссылка представляет собой переход состояния, а форма — действие. То, насколько вам кажется это понимание проницательным или академическим, хорошо указывает на то, как вы в целом отнесетесь к htmx.
Определение размера пакета и то, что он не говорит вам
Указанные размеры могут отличаться, поэтому полезно их измерять. На момент написания минифицированная версия htmx весила примерно столько:
htmx.min.js: 51,238 bytes raw
16,576 bytes gzipped (16.2 KB)
Готовые версии React 19.2.8, пакета React вместе с клиентской частью DOM, имели следующий размер:
react.production.js: 4,446 bytes gzipped
react-dom-client.production.js: 94,757 bytes gzipped
combined: 98,420 bytes gzipped (96 KB)
Это примерно в шесть раз больше, и речь идет только о времени выполнения React. На самом деле в React-приложении присутствуют маршрутизатор, библиотека для управления состоянием, слой захвата данных и собственные компоненты, поэтому размер готовых бандлов обычно достигает нескольких сотен килобайт. В отличие от этого, показатели htmx включают все клиентские зависимости. Точные цифры меняются с каждым выпуском, поэтому необходимо проводить измерения с учетом версий, которые вы действительно будете использовать.
Однако будьте осторожны с выводами. Размер бандла влияет на первую загрузку, но не на скорость последующих взаимодействий. При хорошем соединении разница в времени загрузки незначительна, а при повторных посещениях оба файла загружаются из кэша. Более убедительным аргументом в пользу htmx является отсутствие у него фазы гидратации — периода, когда страница, отрендеренная на сервере, кажется готовой к использованию, но игнорирует клики до тех пор, пока к ней не будут привязаны обработчики событий.
В чем htmx действительно помогает
- Один язык и одна база кода. Команда, работающая с Go, Python, Ruby или C#, может создать всё приложение целиком. Валидация находится в одном месте, а модель данных определяется только один раз.
- Отсутствие конвейера сборки. Достаточно одного тега
script. Нет необходимости в настройке инструментов для сборки, нет папокnode_modulesфронтенда и не требуется постоянное обновление зависимостей фронтенда. - Локальность поведения. Функции элемента описаны непосредственно в самом элементе. Вместо того чтобы следовать за кликом через несколько файлов и хранилищ, достаточно просто прочитать маркировку. Это один из сильнейших аргументов в пользу этого подхода, и он касается удобства обслуживания, а не скорости.
- Состояние в одном месте. Нет необходимости аннулировать кэш клиента, нет устаревших данных и не требуется откат оптимистичных обновлений.
В чем htmx уступает
Это те аспекты, которые часто остаются без внимания.
Насыщенное, непрерывное взаимодействие
Редакторы с функцией перетаскивания элементов, рисование на холсте, таблицы и интерактивные диаграммы с возможностью вращения и увеличения требуют непрерывного взаимодействия, а не отдельных запросов. В htmx каждое взаимодействие представляет собой поездку к серверу туда и обратно. Для такого инструмента, как текстовый редактор, это не является компромиссом; именно поэтому htmx не подходит для него.
Задержки в сетях с плохим качеством
Сторонники htmx отмечают, что хостинг на периферийных серверах значительно сокращает время поездок туда и обратно, и это действительно так. Тем не менее пользователь с слабым мобильным соединением в сельской местности может ждать несколько сотен миллисекунд на выполнение операции, которая в React была бы обработана локально за несколько миллисекунд. Приложения htmx работают отлично в сетях с хорошим качеством, но значительно хуже — в сетях с плохим качеством, что противоречит тому, как обычно представляется этот аргумент.
Отсутствие поддержки мобильных устройств и работы в офлайне
htmx предназначен исключительно для веба. React Native позволяет команде делиться концепциями и значительным объемом кода с нативными приложениями, поэтому если в планах разработка нативных мобильных приложений, это может перевесить все остальные факторы. Офлайн-режим также невозможен, поскольку каждое взаимодействие требует сервера.
Сложное состояние на стороне клиента
Многоэтапные мастер-панели с взаимозависимыми полями, формы с реальной проверкой данных между полями или экраны, где несколько компонентов должны реагировать на одно изменение, создают сложности. Команды обычно добавляют Alpine.js или hyperscript, и тогда они собирают фреймворк из отдельных компонентов вместо того, чтобы использовать готовый фреймворк.
Небольшая экосистема и ограниченный пул специалистов
У React есть зрелые компоненты практически для любого виджета. С htmx вы можете либо создать собственный выбрасчик дат, либо встроить готовый JavaScript-код и самостоятельно управлять его работой. Знание технологий также имеет значение: по данным опросов на момент написания текста, React используют более 40 процентов разработчиков, тогда как htmx — около 7 процентов; соотношение загрузок через npm составляет примерно 560 к 1. Это влияет на скорость того, как новые сотрудники становятся продуктивными, и на легкость поиска решений в случае трудностей.
Тщательное изучение статистики внедрения
Реальная картина более сложная, чем утверждают сторонники тех или иных технологий.
- Использование React не снижается. Он по-прежнему доминирует в абсолютных показателях с очень большим отрывом.
Краткое заключение: React не будет заменён, но его статус безоговорочного стандарта ослабевает. Следует помнить, что большая часть контента о сравнении htmx и React создана для поисковых запросов, а такие показатели, как процент сокращения кода или различные коэффициенты скорости, цитируются без указания источника. Считайте их предварительными оценками и всегда проверяйте первоисточники перед использованием.
Выбор между ними
Выбирайте htmx, когда:
- Приложение в основном состоит из форм и списков: панели администратора, внутренние инструменты, приложения типа CRUD, сайты с контентом, панели управления, отображающие данные, а не модифицирующие их.
- Сильная сторона вашей команды — бэкенд.
- Вам нужна единая база кода, которую сможет поддерживать любой член команды через несколько лет.
Выбирайте React, когда:
- Браузер действительно хранит значительное количество данных, как в редакторах, инструментах дизайна, системах реального времени для совместной работы или при интенсивной обработке данных.
- Вам нужны нативные мобильные приложения или поддержка в офлайн-режиме.
- Вы полагаетесь на зрелую экосистему компонентов.
Существует ещё третий вариант, который часто игнорируется: использовать оба подхода. htmx может отвечать за экраны CRUD, в то время как React обеспечивает работу тех двух-трёх виджетов, которым это действительно необходимо. Ничто не заставляет применять одну архитектуру во всём продукте, и сочетание htmx с фрагментами React проще, чем использование двух фреймворков типа SPA. Если вы уже используете htmx, руководство по обновлению до htmx 4 рассказывает о изменениях в следующей основной версии.
React Server Components заимствуют часть идей гипермедиа, перенося процесс отрисовки на сервер. Однако им всё равно требуется полная среда выполнения React и сервер, совместимый с Node, поэтому это не лёгкий вариант; это React, получающий некоторые из тех же преимуществ при сохранении своей тяжести.
Основные выводы
- htmx позволяет любому элементу отправлять запрос и заменять его на HTML, сгенерированный на сервере, что сохраняет сервер в качестве единственного источника правды и устраняет необходимость синхронизации состояния на клиенте.
- Время выполнения приложения с его использованием составляет примерно одну шестую времени выполнения React до добавления любого кода приложения, но практическая выгода заключается скорее в избежании процесса гидратации, чем в сокращении времени загрузки.
- Он отлично подходит для приложений типа CRUD, внутренних инструментов и сайтов с контентом, но плохо справляется с постоянным взаимодействием, плохой связью, использованием на мобильных устройствах, работой в офлайн-режиме и сложным состоянием на клиенте.
- Сочетание обоих подходов является законной архитектурой, а не компромиссом.
Более важный вопрос, стоящий за этим обсуждением, заключается в том, как архитектура, разработанная для Gmail, стала стандартом для формы с шестью полями. Никто не был полностью неправ: инструмент стал стандартным, а со стандартами перестают разбираться. Что бы вы ни выбрали, включая React, принимайте это решение после тщательных размышлений, а не просто потому, что оно унаследовано.
Связанные статьи
- Скедулер React и цикл событий: кто на самом деле решает, когда выполняться задачи — Узнайте, как кооперативный скедулер React работает внутри цикла событий JavaScript, почему происходят переходы и почему ни один скедулер не может спасти заблокированную нить.