Rails 白话文讲 Turbo Streams

lanzhiheng · 2021年06月10日 · 最后由 MrTentacle 回复于 2022年05月16日 · 1307 次阅读

上一篇文章以白话文的形式简单讲了一下 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 聊一下 turbo stream 提及了此话题。 06月10日 11:46
jicheng1014 回复

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

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
suupic 回复

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

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

需要 登录 后方可回复, 如果你还没有账号请 注册新账号