Come Alive with Turbo Streams



Turbo Streams deliver page changes as fragments of HTML wrapped in self-executing <turbo-stream> elements. Each stream element specifies an action together with a target ID to declare what should happen to the HTML inside it. These elements are delivered by the server over a WebSocket, SSE or other transport to bring the application alive with updates made by other users or processes. A new email arriving in your imbox is a great example.
Turbo Streams将页面更改作为包裹在自动执行的<Turbo stream>元素中的HTML片段。每个流元素指定一个动作和一个目标ID,以声明其内部的HTML应该发生什么。这些元素由服务器通过WebSocket、SSE或其他传输传递,以使应用程序与其他用户或进程进行的更新同步。一封新邮件到达你的邮箱就是一个很好的例子。

Stream Messages and Actions
A Turbo Streams message is a fragment of HTML consisting of <turbo-stream> elements. The stream message below demonstrates the seven possible stream actions:
Turbo Streams消息是由<Turbo stream>元素组成的HTML片段。下面的流消息演示了七种可能的流操作:

<turbo-stream action="append" target="messages">
  <template>
    <div id="message_1">
      This div will be appended to the element with the DOM ID "messages".
    </div>
  </template>
</turbo-stream>

<turbo-stream action="prepend" target="messages">
  <template>
    <div id="message_1">
      This div will be prepended to the element with the DOM ID "messages".
    </div>
  </template>
</turbo-stream>

<turbo-stream action="replace" target="message_1">
  <template>
    <div id="message_1">
      This div will replace the existing element with the DOM ID "message_1".
    </div>
  </template>
</turbo-stream>

<turbo-stream action="update" target="unread_count">
  <template>
    <!-- The contents of this template will replace the
    contents of the element with ID "unread_count" by
    setting innerHtml to "" and then switching in the
    template contents. Any handlers bound to the element
    "unread_count" would be retained. This is to be
    contrasted with the "replace" action above, where
    that action would necessitate the rebuilding of
    handlers. -->
    1
  </template>
</turbo-stream>

<turbo-stream action="remove" target="message_1">
  <!-- The element with DOM ID "message_1" will be removed.
  The contents of this stream element are ignored. -->
</turbo-stream>

<turbo-stream action="before" target="current_step">
  <template>
    <!-- The contents of this template will be added before the
    the element with ID "current_step". -->
    <li>New item</li>
  </template>
</turbo-stream>

<turbo-stream action="after" target="current_step">
  <template>
    <!-- The contents of this template will be added after the
    the element with ID "current_step". -->
    <li>New item</li>
  </template>
</turbo-stream>
Note that every <turbo-stream> element must wrap its included HTML inside a <template> element.
备注,每个 turbo-stream 元素必须封装将html 包含在 <template>元素里面。
You can render any number of stream elements in a single stream message from a WebSocket, SSE or in response to a form submission.
你可以渲染任何数量的stream elements在一条单独的stream消息里面,通过WebSocket, SSE,或者form响应。

Actions With Multiple Targets
Actions can be applied against multiple targets using the targets attribute with a CSS query selector, instead of the regular target attribute that uses a dom ID reference. Examples:
Actions 可以应用到多个targets,通过使用 targets属性,使用一个css查询选择,替换常规的target的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>

Streaming From HTTP Responses
来自HTTP响应的流。
Turbo knows to automatically attach <turbo-stream> elements when they arrive in response to <form> submissions that declare a MIME type of text/vnd.turbo-stream.html. When submitting a <form> element whose method attribute is set to POST, PUT, PATCH, or DELETE, Turbo injects text/vnd.turbo-stream.html into the set of response formats in the request’s Accept header. When responding to requests containing that value in its Accept header, servers can tailor their responses to deal with Turbo Streams, HTTP redirects, or other types of clients that don’t support streams (such as native applications).

Turbo知道,当<Turbo-stream>元素响应<form>提交时,会自动附加<Turbo stream>元素,这些提交声明了一种MIME类型的text/vnd.Turbo-stream.html。当提交方法属性设置为POST、PUT、PATCH或DELETE的<form>元素时,Turbo会注入text/vnd.Turbo-stream.html转换为请求的Accept头中的一组响应格式。当响应Accept报头中包含该值的请求时,服务器可以调整其响应以处理Turbo Streams、HTTP重定向或其他类型的不支持流的客户端(如本机应用程序)。

In a Rails controller, this would look like:
在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
By default, Turbo doesn’t add the text/vnd.turbo-stream.html MIME type when submitting links, or forms with a method type of GET. To use Turbo Streams responses with GET requests in an application you can instruct Turbo to include the MIME type by adding a data-turbo-stream attribute to a link or form.

默认情况下,Turbo不会添加文本/vnd.Turbo-stream.html MIME类型,或方法类型为GET的表单。要在应用程序中使用Turbo Streams响应和GET请求,您可以通过向链接或表单添加data-turbo-stream 来指示Turbo包含MIME类型。

Reusing Server-Side Templates
The key to Turbo Streams is the ability to reuse your existing server-side templates to perform live, partial page changes. The HTML template used to render each message in a list of such on the first page load is the same template that’ll be used to add one new message to the list dynamically later. This is at the essence of the HTML-over-the-wire approach: You don’t need to serialize the new message as JSON, receive it in JavaScript, render a client-side template. It’s just the standard server-side templates reused.

Another example from how this would look in Rails:

Turbo Streams的关键是能够重用现有的服务器端模板来执行实时的部分页面更改。用于在第一次页面加载时呈现此类列表中的每条消息的HTML模板与稍后用于将一条新消息动态添加到列表中的模板相同。这是HTMLover-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
When the form to create a new message submits to the MessagesController#create action, the very same partial template that was used to render the list of messages in MessagesController#index is used to render the turbo-stream action. This will come across as a response that looks like this:
当创建新消息的表单提交给MessagesController#create操作时,用于呈现MessagesContrroller#index 中消息列表的partial template将用于呈现turbo-stream action。这将被理解为一个如下的响应:

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>
This messages/message template partial can then also be used to re-render the message following an edit/update operation. Or to supply new messages created by other users over a WebSocket or a SSE connection. Being able to reuse the same templates across the whole spectrum of use is incredibly powerful, and key to reducing the amount of work it takes to create these modern, fast applications. 

这个 messages/message 模板部分能够用来被渲染这个message,伴随着一个edit/update的操作。
或者去供应一个new messages的创建被其他用户。
能够在整个使用范围内重复使用相同的模板是非常强大的,这是减少创建这些现代、快速应用程序所需工作量的关键。


Progressively Enhance When Necessary
It’s good practice to start your interaction design without Turbo Streams. Make the entire application work as it would if Turbo Streams were not available, then layer them on as a level-up. This means you won’t come to rely on the updates for flows that need to work in native applications or elsewhere without them.

The same is especially true for WebSocket updates. On poor connections, or if there are server issues, your WebSocket may well get disconnected. If the application is designed to work without it, it’ll be more resilient.

在不使用Turbo Streams的情况下开始交互设计是一个很好的做法。如果Turbo Streams不可用,请使整个应用程序正常工作,然后将其分层作为一个级别。这意味着您将不再依赖于需要在本机应用程序或其他地方运行的流的更新。
WebSocket更新尤其如此。如果连接不良,或者存在服务器问题,WebSocket很可能会断开连接。如果应用程序被设计为在没有它的情况下工作,它将更有弹性。

But What About Running JavaScript?
Turbo Streams consciously restricts you to seven actions: append, prepend, (insert) before, (insert) after, replace, update, and remove. If you want to trigger additional behavior when these actions are carried out, you should attach behavior using Stimulus controllers. This restriction allows Turbo Streams to focus on the essential task of delivering HTML over the wire, leaving additional logic to live in dedicated JavaScript files.

Embracing these constraints will keep you from turning individual responses in a jumble of behaviors that cannot be reused and which make the app hard to follow. The key benefit from Turbo Streams is the ability to reuse templates for initial rendering of a page through all subsequent updates.

Turbo Streams有意识地将您限制为七个操作:追加、预置、(插入)之前、(插入(插入)之后、替换、更新和删除。如果要在执行这些操作时触发其他行为,应使用Stimulus控制器附加行为。这一限制使Turbo Streams能够专注于通过网络交付HTML的基本任务,从而在专用的JavaScript文件中保留额外的逻辑。
接受这些约束将使您避免在一系列无法重用的行为中转换单个响应,从而使应用程序难以遵循。Turbo Streams的主要好处是能够在所有后续更新中重用模板进行页面的初始渲染。

Integration with Server-Side Frameworks
Of all the techniques that are included with Turbo, it’s with Turbo Streams you’ll see the biggest advantage from close integration with your backend framework. As part of the official Hotwire suite, we’ve created a reference implementation for what such an integration can look like in the turbo-rails gem. This gem relies on the built-in support for both WebSockets and asynchronous rendering present in Rails through the Action Cable and Active Job frameworks, respectively.

Using the Broadcastable concern mixed into Active Record, you can trigger WebSocket updates directly from your domain model. And using the Turbo::Streams::TagBuilder, you can render <turbo-stream> elements in inline controller responses or dedicated templates, invoking the five actions with associated rendering through a simple DSL.

Turbo itself is completely backend-agnostic, though. So we encourage other frameworks in other ecosystems to look at the reference implementation provided for Rails to create their own tight integration.

Turbo’s <turbo-stream-source> custom element connects to a stream source through its [src] attribute. When declared with an ws:// or wss:// URL, the underlying stream source will be a WebSocket instance. Otherwise, the connection is through an EventSource.

When the element is connected to the document, the stream source is connected. When the element is disconnected, the stream is disconnected.

Since the document’s <head> is persistent across Turbo navigations, it’s important to mount the <turbo-stream-source> as a descendant of the document’s <body> element.

Typical full page navigations driven by Turbo will result in the <body> being discarded and replaced with the resulting document. It’s the server’s responsibility to ensure that the element is present on any page that requires streaming.

Alternatively, a straightforward way to integrate any backend application with Turbo Streams is to rely on the Mercure protocol. Mercure defines a convenient way for server applications to broadcast page changes to every connected clients through Server-Sent Events (SSE). Learn how to use Mercure with Turbo Streams.

在Turbo包含的所有技术中,使用Turbo Streams,您将看到与后端框架紧密集成的最大优势。作为官方Hotwire套件的一部分,我们为turbo-rails gem中的这种集成创建了一个参考实现。这个gem依赖于分别通过Action Cable和Active Job框架在Rails中提供的对WebSockets和异步渲染的内置支持。
使用混合到Active Record中的Broadcastable关注点,您可以直接从域模型触发WebSocket更新。使用Turbo::Streams::TagBuilder,您可以在内联控制器响应或专用模板中渲染<Turbo stream>元素,通过简单的DSL调用五个相关渲染动作
不过,Turbo本身完全是后端不可知论者。因此,我们鼓励其他生态系统中的其他框架研究为Rails提供的参考实现,以创建自己的紧密集成。
Turbo的<Turbo-stream-source>自定义元素通过其[src]属性连接到流源。当使用ws://或wss://URL声明时,底层流源将是WebSocket实例。否则,连接是通过EventSource进行的。
当元素连接到文档时,流源被连接。当元素断开连接时,流断开连接。
由于文档的<head>在Turbo导航中是持久的,所以将<Turbo stream source>作为文档<body>元素的后代挂载是很重要的。
由Turbo驱动的典型的全页导航将导致<body>被丢弃并替换为生成的文档。服务器有责任确保元素出现在任何需要流媒体的页面上。
或者,将任何后端应用程序与Turbo Streams集成的直接方法是依赖Mercure协议。Mercure为服务器应用程序定义了一种通过服务器发送事件(SSE)向每个连接的客户端广播页面更改的便捷方式。了解如何将Mercure与Turbo Streams结合使用。

阅读量: 616
发布于:
修改于: