Добавляем жизнь с помощью Turbo Streams - geroi/turbo-rails GitHub Wiki
Turbo Streams передают изменения страницы в виде фрагментов HTML
, обернутых в самоисполняющиеся элементы <turbo-stream>
. Каждый элемент потока определяет действие вместе с идентификатором цели, чтобы объявить, что должно произойти с HTML
внутри него. Эти элементы доставляются сервером через WebSocket, SSE или другой транспорт, чтобы оживить приложение обновлениями, сделанными другими пользователями или процессами. Отличным примером является новое письмо, приходящее в ваш почтовый ящик.
﹟Потоковые сообщения и действия
Сообщение Turbo Streams - это фрагмент HTML
, состоящий из элементов <turbo-stream>
. Приведенное ниже сообщение стрима демонстрирует семь возможных действий стрима:
<turbo-stream action="append" target="messages">
<template>
<div id="message_1">
Этот div будет добавлен после элемента с идентификатором DOM "messages".
</div>
</template>
</turbo-stream>
<turbo-stream action="prepend" target="messages">
<template>
<div id="message_1">
Этот div будет добавлен перед элементом с идентификатором DOM "messages".
</div>
</template>
</turbo-stream>
<turbo-stream action="replace" target="message_1">
<template>
<div id="message_1">
Этот div заменит существующий элемент с DOM ID "message_1".
</div>
</template>
</turbo-stream>
<turbo-stream action="update" target="unread_count">
<template>
<!-- Содержимое этого шаблона заменит
содержимое элемента с идентификатором "unread_count" путем
установив innerHtml в "", а затем переключившись в
содержимое шаблона. Любые обработчики, привязанные к элементу
"unread_count" будут сохранены. Это должно быть
в отличие от действия "заменить", описанного выше, где
это действие потребует перестройки
обработчики. -->
1
</template>
</turbo-stream>
<turbo-stream action="remove" target="message_1">
<!-- Элемент с DOM ID "message_1" будет удален.
Содержимое этого элемента потока игнорируется. -->
</turbo-stream>
<turbo-stream action="before" target="current_step">
<template>
<!-- Содержимое этого шаблона будет добавлено перед
элемента с ID "current_step". -->
<li>New item</li>
</template>
</turbo-stream>
<turbo-stream action="after" target="current_step">
<template>
<!-- Содержимое этого шаблона будет добавлено после
элемента с ID "current_step". -->
<li>New item</li>
</template>
</turbo-stream>
Обратите внимание, что каждый элемент <turbo-stream>
должен обернуть свой включенный HTML внутри элемента <template>
.
Вы можете отобразить любое количество элементов потока в одном потоковом сообщении из WebSocket
, SSE
или в ответ на отправку формы.
Действия можно применять к нескольким целям, используя атрибут targets
с селектором запроса CSS
, вместо обычного атрибута target
, который использует ссылку на DOM
id
. Примеры:
<turbo-stream action="remove" targets=".old_records">
<!-- The element with the class "old_records" will be removed.
The contents of this stream element are ignored. -->
</turbo-stream>
<turbo-stream action="after" targets="input.invalid_field">
<template>
<!-- The contents of this template will be added after the
all elements that match "inputs.invalid_field". -->
<span>Incorrect</span>
</template>
</turbo-stream>
Turbo умеет автоматически присоединять элементы <turbo-stream>
, когда они приходят в ответ на запросы <form>
, в которых объявлен MIME-тип text/vnd.turbo-stream.html
. При отправке элемента <form>
, атрибут method которого установлен в POST, PUT, PATCH или DELETE
, Turbo вставляет text/vnd.turbo-stream.html
в набор форматов ответа в заголовке Accept
запроса. Отвечая на запросы, содержащие это значение в заголовке Accept
, серверы могут адаптировать свои ответы для работы с Turbo Streams, HTTP
-перенаправлениями или другими типами клиентов, которые не поддерживают потоки (например, нативные приложения).
В контроллере Rails это будет выглядеть следующим образом:
def destroy
@message = Message.find(params[:id])
@message.destroy
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.remove(@message) }
format.html { redirect_to messages_url }
end
end
Ключом к Turbo Streams является возможность повторного использования существующих шаблонов на стороне сервера для выполнения живых, частичных изменений страницы. HTML
-шаблон, используемый для отображения каждого сообщения в списке при первой загрузке страницы, - это тот же шаблон, который будет использоваться для динамического добавления одного нового сообщения в список позже. В этом заключается суть подхода HTML-over-the-wire
: Вам не нужно сериализовать новое сообщение как JSON
, получать его в JavaScript
, отрисовывать шаблон на стороне клиента. Это просто повторное использование стандартных шаблонов на стороне сервера.
Еще один пример того, как это будет выглядеть в Rails:
<!-- app/views/messages/_message.html.erb -->
<div id="<%= dom_id message %>">
<%= message.content %>
</div>
<!-- app/views/messages/index.html.erb -->
<h1>All the messages</h1>
<%= render partial: "messages/message", collection: @messages %>
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
def index
@messages = Message.all
end
def create
message = Message.create!(params.require(:message).permit(:content))
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.append(:messages, partial: "messages/message",
locals: { message: message })
end
format.html { redirect_to messages_url }
end
end
end
Когда форма для создания нового сообщения отправляется на действие MessagesController#create
, тот же самый частичный шаблон, который использовался для отображения списка сообщений в MessagesController#index
, используется для отображения действия turbo-stream
. В результате ответ будет выглядеть следующим образом:
Content-Type: text/vnd.turbo-stream.html; charset=utf-8
<turbo-stream action="append" target="messages">
<template>
<div id="message_1">
The content of the message.
</div>
</template>
</turbo-stream>
Эти сообщения/части шаблона сообщения могут быть использованы для повторного отображения сообщения после операции редактирования/обновления. Или для предоставления новых сообщений, созданных другими пользователями через WebSocket или SSE-соединение. Возможность повторного использования одних и тех же шаблонов по всему спектру применения является невероятно мощной и ключевой для сокращения объема работы, необходимой для создания современных, быстрых приложений.
Хорошая практика - начинать проектирование взаимодействия без Turbo Streams. Заставьте все приложение работать так, как оно работало бы при отсутствии Turbo Streams, а затем добавьте их в качестве уровня. Это означает, что вы не будете полагаться на обновления для потоков, которые должны работать в нативных приложениях или в других местах без них. То же самое особенно верно для обновлений WebSocket. При плохом соединении или проблемах с сервером ваш WebSocket вполне может быть отключен. Если приложение рассчитано на работу без него, оно будет более устойчивым.
Turbo Streams сознательно ограничивает вас семью действиями: append
, prepend
, (insert) before
, (insert) after
, replace
, update
и remove
. Если вы хотите вызвать дополнительное поведение при выполнении этих действий, вы должны прикрепить поведение с помощью контроллеров Stimulus. Это ограничение позволяет Turbo Streams сосредоточиться на основной задаче - передаче HTML
по проводам, оставляя дополнительную логику в специальных файлах JavaScript
.
Принятие этих ограничений позволит вам не превратить отдельные ответы в нагромождение поведений, которые невозможно повторно использовать и которые делают приложение трудноуправляемым. Ключевым преимуществом Turbo Streams является возможность повторного использования шаблонов для начальной визуализации страницы во всех последующих обновлениях.
Из всех методов, входящих в Turbo, именно в Turbo Streams вы увидите наибольшее преимущество от тесной интеграции с вашим бэкенд-фреймворком. Как часть официального пакета Hotwire, мы создали эталонную реализацию того, как может выглядеть такая интеграция, в геме turbo-rails
. Этот гем опирается на встроенную поддержку WebSockets и асинхронного рендеринга, присутствующую в Rails через фреймворки Action Cable и Active Job, соответственно.
Используя концерн Broadcastable, встроенный в Active Record,
вы можете вызывать обновления WebSocket
непосредственно из вашей доменной модели. А используя Turbo::Streams::TagBuilder
, вы можете отображать элементы <turbo-stream>
во встроенных ответах контроллера или специальных шаблонах, вызывая пять действий с сопутствующей визуализацией через простой DSL
.
Однако сам Turbo полностью независим от бэкенда. Поэтому мы рекомендуем другим фреймворкам из других экосистем обратиться к эталонной реализации, предоставленной для Rails, чтобы создать свою собственную тесную интеграцию.
Альтернативным способом интеграции любого бэкенд-приложения с Turbo Streams является протокол Mercure. Mercure определяет удобный способ для серверных приложений транслировать изменения страниц всем подключенным клиентам с помощью событий, отправляемых сервером (SSE). Узнайте, как использовать Mercure с Turbo Streams.