最近看到被雅虎收购后关掉的社会化书签网站del.icio.us又更新了内容,一个叫 Maciej Ceglowski 的家伙拥有了 del.icio.us 的域名,估计又要重新搞起?
自从 google reader 被关掉、del.icio.us 被关掉,获取信息的渠道少了,我主要通过 github 和 twitter,用 pocket 来保存书签,但 pocket 更偏向收藏。
由于很久没有写 Web,HTML CSS JavaScript 这些快忘的差不多了,抱着重新学习一下前端和做一个社会化标签网站的想法,注册了一个域名 hackershare.dev.
产品的设计功能点:
下面写一下重新使用 Rails 来做 Web 的一些体验,技术栈主要是 Postgresql 12+,Rails6,Turbolinks 5,Rails UJS 和 Stimulusjs
拿收藏功能来举例,使用 stimulusjs+rails-ujs 来演示一下 Rails 的新三板斧。页面效果:
ERB 片段:
<span data-controller="likes" class="relative z-0 inline-flex shadow-sm rounded-md">
<%= link_to toggle_liking_bookmark_path(bookmark), method: :post, type: 'button', remote: true, data: {type: :json, action: "ajax:success->likes#toggle"}, class: "relative inline-flex items-center px-4 py-2 rounded-l-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150" do %>
<svg data-target="likes.svg" class="h-5 w-5 <%= bookmark.liked_by?(current_user) ? "text-yellow-300 hover:text-yellow-400" : "text-gray-300 hover:text-gray-400" %>" viewBox="0 0 20 20" fill="currentColor">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
<% end %>
<button data-target="likes.data" type="button" class="-ml-px relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
<%= bookmark.likes_count %>
</button>
</span>
首先是 UJS,
<%= link_to toggle_liking_bookmark_path(bookmark), method: :post, type: 'button', remote: true, data: {type: :json, action: "ajax:success->likes#toggle"}
link_to 带 remote: true 之后 ujs 发起一个 ajax 请求,type 是 json,和 jquery-ujs 那时候的写法一致,只不过之前一般都是服务端返回 JavaScript 操作 DOM。现在有了 stimulusjs 层,返回 JavaScript 和 HTML 还有 JSON 都很方便处理。下面说 stimulusjs:
HTML 里的data-controller
和data-action
还有data-target
作用是事件绑定。收藏这个功能就是 ujs 发起 ajax 请求成功之后,我们要更新 button 颜色,更新 likes.data 区域的数量。
// likes_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "data", "svg" ]
connect() {
}
toggle(event){
let [data, status, xhr] = event.detail;
if (typeof(data) === 'string') {
// 这里如果返回的是JavaScript或html,说明是未登录情况,turbolinks直接给跳转了
return;
}
if (data["like"]) {
this.svgTarget.classList.remove("text-gray-300");
this.svgTarget.classList.remove("hover:text-gray-400");
this.svgTarget.classList.add("text-yellow-300");
this.svgTarget.classList.add("hover:text-yellow-400");
} else {
this.svgTarget.classList.remove("text-yellow-300");
this.svgTarget.classList.remove("hover:text-yellow-400");
this.svgTarget.classList.add("text-gray-300");
this.svgTarget.classList.add("hover:text-gray-400");
}
this.dataTarget.innerHTML = data["bookmark"]["likes_count"];
}
}
上面就是 stimulusjs 的全部代码,data: {action: "ajax:success->likes#toggle"}
让 ajax 成功之后调用 likes_controller.js 的 toggle 方法,并且可以取得到 event 对象,这里来处理 ajax 请求之后的页面效果。无论是后端返回 HTML 还是 JSON 还是 JavaScript,相对旧的 jquery-ujs 的方式,在 stimulusjs 这层里处理都非常容易。并且 JavaScript 有了新的组织,统一在 app/javascrips/controllers 目录,每个功能单独一个 controller。
当然,上面的功能还可以用另外的方式来实现,抛弃 rails-ujs,直接 stimulusjs 绑定 link 的 click 事件,在 likes_controller.js 里去做 ajax 请求 + 页面处理的工作,但我觉得 ujs+stimulusjs 这种更简单一些。
下面说一下 turbolinks 5 和 ujs 的一些坑,我们知道使用 turbolinks 之后,内部链接之间切换不会刷新页面,体验要比每次重新刷新页面好很多,但对于服务器端来说,整个页面还是要渲染一遍,相关的查询也要执行一遍,想有更好的体验也需要一些专门的 ajax 的请求,返回局部内容,比如分页和过滤场景。
但目前版本的 rails-ujs 和 turbolinks 5 还不是兼容的很好,拿分页举例,下一页按钮由 rails-ujs 触发,使用 pushState 接口同步 URL 历史,便于收藏,但由于这个请求并不是 turbolinks 参与的,turbolinks 维护的快照会缺失,当浏览器点后退或前进时,rails-ujs 渲染的部分会丢掉,体验很差。一个 hack 的解法是,把浏览器 restoration visit 屏蔽掉,统一 reload,由于浏览器后退的操作其实并不是高频,并不会代理什么影响:
window.onpopstate = function(event) {
document.location.reload();
};
不知道 Turblinks 6 会不会把这个统一做好,或者抛弃 rails-ujs,让 turbolinks 来完成 ajax 的局部刷新功能。总体来说 rails-ujs+Stimulusjs+turbolinks 5 这套组合拳还是非常棒的,并且是真的渐进增强体验,比如分页和过滤这种,关闭了 JS,页面渲染还是正常,一点不影响 SEO. 只需要一行额外代码:
respond_to do |format|
format.js { render partial: "bookmarks/bookmarks_with_pagination", content_type: "text/html" }
format.html
end