Rails Turbo/Hotwire 指南

无需大量 JavaScript 构建类 SPA 的 Rails 应用:Turbo Drive、Turbo Frames、Turbo Streams 和 Stimulus。

1. Turbo Drive

<!-- Turbo Drive 拦截所有链接点击和表单提交,仅替换 <body> -->

<!-- 禁用特定链接 -->
<a href="/download" data-turbo="false">下载 PDF</a>

# 控制器 — 创建后重定向
class ArticlesController < ApplicationController
  def create
    @article = Article.new(article_params)
    if @article.save
      redirect_to @article, notice: "文章已创建!"
    else
      render :new, status: :unprocessable_entity
    end
  end
end

2. Turbo Frames

<!-- 用 turbo-frame 包裹区域 -->
<turbo-frame id="article_form">
  <%= render "form", article: @article %>
</turbo-frame>

<!-- 懒加载框架 -->
<turbo-frame id="sidebar_stats" src="/stats/sidebar" loading="lazy">
  <p>加载中...</p>
</turbo-frame>

3. Turbo Streams

class CommentsController < ApplicationController
  def create
    @comment = @article.comments.build(comment_params.merge(user: current_user))
    if @comment.save
      respond_to do |format|
        format.turbo_stream do
          render turbo_stream: [
            turbo_stream.prepend("comments", partial: "comment", locals: { comment: @comment }),
            turbo_stream.update("comment_form", partial: "form", locals: { comment: Comment.new }),
          ]
        end
        format.html { redirect_to @article }
      end
    end
  end
end

4. Stimulus 控制器

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "count"]
  static values  = { max: { type: Number, default: 280 } }

  update() {
    const remaining = this.maxValue - this.inputTarget.value.length
    this.countTarget.textContent = remaining
  }
}

<!-- HTML -->
<div data-controller="character-count">
  <textarea data-character-count-target="input"
            data-action="input->character-count#update"></textarea>
  <span data-character-count-target="count"></span> 剩余
</div>