Rails 白话文讲 Turbo Streams

lanzhiheng · June 10, 2021 · Last by MrTentacle replied at May 16, 2022 · 1215 hits

上一篇文章以白话文的形式简单讲了一下 Turbo Frames,这篇文章打算介绍 Turbo 的最后一个部件 Turbo Streams。同样我会结合实际场景提供代码示例。原文链接 https://www.lanzhiheng.com/posts/turbo-streams-simple-talking

Turbo Frames 的问题

上一篇文章讲过 Turbo Frames 的作用跟 iframe 很像,设定一个区域,区域中的任何操作都只会影响该区域的内容。在不需要编写 JavaScript 的前提下这种做法还是挺方便的,不过也有它的局限性每次请求的操作有限,只能影响区域内的内容。如果想要页面其他区间作出响应,依旧需要额外编写 JavaScript 代码。.

Turbo Streams进一步增强了我们对页面的操控能力。

Turbo Streams deliver page changes as fragments of HTML wrapped in self-executing elements. Each stream element specifies an action together with a target ID to declare what should happen to the HTML inside it.

依然是不需要写 JavaScript 代码,后端服务以声明的方式提供 HTML 模板,以 DOM 的 id 作为唯一标识,并配上对应的动作,Turbo Streams 接收到这个模板之后就会

  1. 在页面中找到对应 id 的节点。
  2. 执行相关的动作。

模板大概长这个样子

<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>

篇幅有限我就不全部贴了,官方文档都有。这里表示它会寻找 id 为messages的容器,然后往里面追加template标签中的内容。类似的动作还有prepend, replace, update, remove,意思也就是字面上的意思。

也就是说,它只提供了较为基本的 5 个操作,足以满足日常工作中的大部分 DOM 操作需求。如果需要更加复杂的交互,再考虑使用 JavaScript 来专门解决。

一次请求,多处变动

先把 WebSocket 或者 SSE 放一边,看看普通的请求模式 Turbo Streams 的能力。下面是我最近对博客系统管理后台的重写,需求很简单

  1. 提供一个文章上架/下架的按钮。
  2. 点击按钮往后台发送请求。
  3. 请求完成之后 1)按钮的文字要变换。2. 列表中对应状态要改变。3. 提供对应的 flash 信息。

change-area.png

效果如下:

toggle-change.gif

虽然对一个自己用的系统来说,这种做法稍微有点过度优化了,直接跳页面岂不是简单很多?不过只要依赖于 Turbo Streams 做成这种交互并不会比重新刷页面要复杂多少,姑且一试。

Controller 部分很简单,只需要提供turbo_stream的对应响应即可。

module Musk
  class PostsController < ApplicationController
    def toggle
      original = resource.draft
      resource.update(draft: !original)
      message = original ? '上架成功' : '下架成功'
      flash.now[:notice] = "《#{resource.title}#{message}"

      respond_to do |format|
        format.turbo_stream { render :toggle, locals: { post: resource } } # turbo_stream响应
        format.html { redirect_back(fallback_location: collection_path) }
      end
    end
  end
end

这里要提供两种渠道,一种是响应 HTML 的,另一种是响应 Turbo Streams,这个后台会根据请求头的Accept字段来判断。

accept-header.png

接下来只需要像传统的 Rails 项目那样提供对应的模板就行,只不过这次的模板的后缀是turbo_stream.erb

<!-- app/views/musk/posts/toggle.turbo_stream.erb -->

<!-- 修改对应资源在表格中的状态,假设资源的id为1,那么就修改id="table_online_1"的单元格 -->
<turbo-stream action="update" target=<%= "table_online_#{post.id}" %>>
  <template>
    <%= boolean_tag(post.online) %>
  </template>
</turbo-stream>

<!-- 修改触发按钮的内容,“上架”按钮,请求结束后会变成“下架”按钮,反之亦然 -->
<turbo-stream action="replace" target=<%= "toggle_link_#{post.id}" %>>
  <template>
    <%= toggle_link(post) %>
  </template>
</turbo-stream>

<!-- 显示flash信息,注意我这里用的是flash.now,因为只需要对当前渲染生效,页面跳转后消失 -->
<turbo-stream action="update" target="alert-area">
  <template>
    <%= alert_area %>
  </template>
</turbo-stream>

简单到有点难以置信吧?篇幅有限,boolean_tag, toggle_link, alert_area这些帮助方法我就不贴了,大家可以根据自己的需要去定制。如果需要参考相关源码可以到笔者的Stone仓库查看。

可见只需要少量的代码,配合服务端渲染,不需要写任何 JavaScript 代码,就能同时操作页面的不同区域,只不过要事先给待调整的地方做上id唯一标记。这对于熟悉 Rails,并不想写太多 JavaScript 的人来说确实是个不错的工具。我们能够很容易就写出交互性较好的页面。

总结

这篇文章通过案例简单讲解了一下 Turbo Streams 的基本用法,由于笔者还没有 Websocket 或者 SSE 相关的使用场景,这个放在以后再说。作为对 Turbo Frames 的补充,Turbo Streams 只提供了 5 种(append, prepend, replace, update, remove)基本操作。不过对于大多数场景来说已然足够,或许也是符合 80/20 的原则?然而作为一个依赖服务端渲染的工具集 Turbo Streams 依然有它的局限性,毕竟还是有一些场景 5 种基本操作无法满足的,这个时候我们就需要依赖 JavaScript 去解决了,接下来的Stimulus框架是 Rails 社区的首选。

老哥 你那个录屏生成 GIF 的工具是啥呀 看着好洋气

jicheng1014 in 聊一下 turbo stream mention this topic. 10 Jun 11:46
Reply to jicheng1014

😂 我只是用 Mac 的 QuickTime 录制屏幕,然后在 https://cloudconvert.com/mov-to-gif 这里转一下。

Reply to jicheng1014

我太傻了有现成的不用,你试一下这个

效果

就是高清文件有点大,不好刷出来。

把多个动作放在一个 view 里不是很灵活,不好复用,可以拆成多个碎片,然后用数组 render:

format.turbo_stream do
   render turbo_stream: [
     turbo_stream.update(:flash, partial: 'shared/flash'),
     turbo_stream.update('action-1-id', partial: 'action-1-view'),
     turbo_stream.update('action-2-id', partial: 'action-2-view'),
     ...
   ]
end
Reply to suupic

有道理,回头试试,确实多了之后还是需要拆分一下。后面看看能不能在 view 里面 render partial。

也就是说 现在想在使用 Turbo Streams 的同时触发其他 JS 的话 得用 Stimulus 支持咯?不能在像以前那样只是给个 模版同名的.js.erb 文件了是么?

You need to Sign in before reply, if you don't have an account, please Sign up first.