Turbo Stream Actions: интерактивный атлас DOM-операций
Turbo Stream - это маленькая декларативная команда над уже существующим DOM: найти цель, взять HTML из <template> и выполнить одну операцию. Чтобы понять эту механику, не нужны Rails-контроллер, WebSocket или даже сетевой запрос. Достаточно увидеть, какие именно узлы остаются, перемещаются и исчезают.
Ниже - интерактивный атлас всех восьми стандартных действий из Turbo Handbook. Лаборатория симулирует DOM-эффект прямо в статье. Она намеренно не объясняет доставку сообщений, broadcasting и серверный рендеринг: только формат элемента и результат его встраивания в страницу.
Одна команда состоит из четырёх частей
<turbo-stream action="append" target="messages"> <template> <article>Новое сообщение</article> </template> </turbo-stream>
<turbo-frame>. Это может быть <div>, строка таблицы, карточка, счётчик или любой другой элемент документа, который находится по ID либо CSS-селектору.Лаборатория: что визуально делает каждое action
Выберите действие, затем одиночную или множественную цель и нажмите «Применить». Синяя рамка показывает узлы, которые Turbo найдёт до операции. Команда слева меняется вместе с настройками.
#message_2
Восемь действий, сгруппированные по геометрии
Названия легче запомнить не списком, а по тому, где находится граница изменения.
↓append
Добавляет содержимое шаблона последними дочерними узлами внутрь цели. Сама цель остаётся.
↑prepend
Добавляет содержимое первыми дочерними узлами внутрь цели. Сама цель остаётся.
←before
Вставляет шаблон рядом с целью, непосредственно перед ней. Новый узел становится её sibling.
→after
Вставляет шаблон рядом с целью, непосредственно после неё. Новый узел становится её sibling.
□replace
Удаляет сам целевой элемент и ставит на его место шаблон. Старая оболочка и её listeners исчезают.
▣update
Сохраняет целевой элемент, но заменяет его дочерние узлы. Оболочка, атрибуты и привязанные к ней listeners остаются.
×remove
Удаляет цель. <template> не нужен, потому что вставлять нечего.
↻refresh
Запускает обновление страницы. У него нет цели и шаблона: это действие над визитом, а не над отдельным узлом.
| action | Что сохраняется | Где оказывается template | Типичный смысл |
|---|---|---|---|
| append | цель и текущие children | в конце цели | добавить запись в список |
| prepend | цель и текущие children | в начале цели | показать самое новое первым |
| before | цель целиком | перед целью | вставить соседнюю строку |
| after | цель целиком | после цели | добавить пояснение или продолжение |
| replace | ничего от старой цели | вместо цели | перерисовать компонент |
| update | оболочка цели | внутрь очищенной цели | сменить содержимое счётчика или панели |
| remove | соседи цели | шаблона нет | убрать запись |
| refresh | зависит от page refresh strategy | шаблона нет | обновить страницу целиком |
Тонкость append и prepend: совпавший ID заменяется
Если первый элемент шаблона имеет ID, который уже принадлежит прямому ребёнку целевого контейнера, Turbo заменит существующего ребёнка вместо добавления дубликата. Это делает повторное добавление записи идемпотентнее, но проверка относится именно к прямым children.
morph - не девятое action. Это метод выполнения для replace или update: method="morph". Обычный вариант без атрибута жёстко меняет узел или children; morph пытается согласовать старое и новое дерево и сохранить подходящие DOM-узлы. Поэтому его эффект зависит от структуры и стабильных ID.Actions With Multiple Targets
target="message_2" означает поиск одного элемента по DOM ID. targets=".pending" означает querySelectorAll(".pending"): одна и та же операция применяется ко всем найденным элементам, а содержимое шаблона клонируется для каждой цели.
CSS selector playground
Попробуйте селекторы .pending, .priority, [data-owner="me"] или .task:not(.done). Селектор вычисляется в момент выполнения команды. Если совпадений нет, DOM просто не изменится. Если CSS-селектор синтаксически неверен, команда не сможет разрешить цели.
targets повторяет одно action над набором узлов. Чтобы сделать prepend в одном месте, update в другом и remove в третьем, нужны несколько элементов <turbo-stream>.Несколько команд выполняются по порядку
Одно stream-сообщение может содержать несколько соседних <turbo-stream>. Turbo исполняет их в порядке появления. Это отдельная ось композиции: targets размножает одну операцию, а несколько stream-элементов описывают сценарий из разных операций.
Две операции, один локальный сценарий
<turbo-stream action="prepend" target="wishlist_items"> <template>...Работа...</template> </turbo-stream> <turbo-stream action="update" target="quick_create"> <template>Список создан</template> </turbo-stream>
Первая команда добавляет только новую строку. Вторая меняет только содержимое формы быстрого создания. Уже существующие строки не перерисовываются, поэтому несохранённый выбор чекбокса остаётся в браузере. Это и есть преимущество маленьких DOM-операций: граница изменения видна прямо в разметке сообщения.
Как выбирать действие
append или prepend. Выберите сторону, где пользователь ожидает новую запись.replace, если нужна новая оболочка; update, если оболочка является стабильным контрактом.targets с узким CSS-селектором. Для разных правок используйте несколько stream-элементов.Практическое правило то же, что и для любых частичных обновлений: выбирайте минимальную область, которая действительно устарела. Чем крупнее цель, тем выше шанс потерять фокус, значение поля, выделение текста, прокрутку или состояние стороннего виджета.
Короткая проверка перед использованием
- Стабилен ли ID цели между рендерами?
- Нужно сохранить сам элемент или только его место? Это выбор между
updateиreplace. - Есть ли внутри цели несохранённое браузерное состояние?
- Нужна одна операция для набора элементов или разные операции для разных мест?
- Останется ли новая разметка доступной для следующей команды по тому же ID или селектору?
Для общей картины о навигационных областях вернитесь к статье «Turbo Frames с нуля». Там Frame рассматривается как контекст перехода, а Stream - как точечная DOM-команда. Здесь мы намеренно разобрали только вторую половину.