用 Hotwire Turbo 实现常驻侧边栏

最近用 Hotwire Turbo 改进了 Geeknote 的首页,减少了重复查询和渲染,想分享一下过程。

问题

Geeknote 的首页有个侧栏区域,区域内展示了一些次要内容,有热门标签、推荐用户,和社区统计等,未来还可能加一些广告代码或友情链接。

问题在于,这个区域的内容并不重要,但是需要的查询并不少。热门标签需要对近期标签使用进行统计排序,推荐用户需要关联当前用户是否关注,社区统计比较简单只是对几个表的总计数。在用户切换 Tab 的时候会反复执行这些查询,并且推荐用户模块由于加了随机性,导致内容不断变动,会引起不必要的关注。

我希望侧栏内容在切换类似页面的时候持久化,减少查询,于是想到了用 Turbo 改造这块区域。

解决

原本的首页渲染基于普通的服务端模版,内容类似于:

<!-- home.html.erb -->
<div class="sidebar">
  <!-- hot tags -->
  <!-- recommend users -->
  <!-- stats -->
</div>

要让这块内容持久化,需要做两个事情:

  • 用 Turbo Frame 实现异步加载
  • data-turbo-permanent 实现持久化

异步加载

新增一个 action:

class HomeController < ApplicationController
  def sidebar
  end
end

添加相应的路由:

  get "home/sidebar", to: "home#sidebar", as: :home_sidebar

将原先的侧栏模版移到新的视图:

<!-- home/sidebar.html.erb -->
<turbo-frame id="home_sidebar">
  <div class="sidebar">
    <!-- hot tags -->
    <!-- recommend users -->
    <!-- stats -->
  </div>
</turbo-frame>

注意这里加了一层 <turbo-frame> 标签,这是用来标识后面 turbo frame 需要替换的内容。

如果一切正常,可以访问 /home/sidebar 预览侧栏的内容。你会发现由于没有其他布局元素,侧栏内容显得很宽,如果想好看一点可以在 <turbo-frame> 外层加一些布局元素,例如设置最大宽度和居中显示,这不会影响最终结果。

接着在原先 home 模版 sidebar 的位置添加标签:

<!-- home.html.erb -->
<turbo-frame id="home_sidebar" src="/home/sidebar" target="_top">
<turbo-frame>

解释一下内容:

  • <turbo-frame src="/home/sidebar"> 表示这块内容来自于 /home/sidebar
  • id="home_sidebar" 表示请求目标页面后,用页面内相同 id 的 turbo_frame 内容替换这个 turbo_frame。(对应于 sidebar 模版内的 turbo_frame)
  • target="_top" 表示点击 turbo_frame 内的链接将导航整个页面,而不局限于 turbo_frame 以内。

完成以后预览效果,可以看到侧栏是异步加载的:

咋一看这好像是负优化,每次切换 Tab 都发出两个请求,重复查询和推荐用户一直变动的问题也没解决。接下来我们让侧栏持久化。

持久化

<turbo-frame> 上添加一个 data-turbo-permanent 属性:

<!-- home.html.erb -->
<turbo-frame id="home_sidebar" src="/home/sidebar" target="_top" data-turbo-permanent>
<turbo-frame>

完成了!这个属性告诉 Turbo,在换页的时候把这个标签视为持久化内容,将上一页的内容直接移动到新页面。而在上一个页面这个 turbo_frame 已经加载过,所以不会再次加载。

效果如下:

在第一次访问首页的时候,依然需要访问一次 home/sidebar,但之后切换 Tab,侧栏内容不会重复请求。

这就解决了侧栏重复查询和内容不断变动的问题。

总结

这是一个用 Hotwire 渐进式增强交互体验的例子。我可以先用传统的服务端渲染实现所有功能,然后再挑选值得优化的地方改进。

试想一下如果是前后端分离的项目,可能一开始就为侧栏内容设计多个 API,然后渲染逻辑在前端实现。但对我来说,一开始填充侧栏内容的时候还没想好要展示什么,服务端渲染可以轻易的尝试不同的展示逻辑,在服务端用变量传递数据要比 API 轻量很多。

希望这篇文章能为你提供帮助。

2
所有评论 0
@Rei
Ruby 程序员,Ruby China 管理员,GeekNote 创建者。