hotwire

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hotwire, Turbo & Stimulus for Rails

适用于Rails的Hotwire、Turbo & Stimulus

Expert patterns for JavaScript and Hotwire integration with Ruby on Rails.
Ruby on Rails与JavaScript及Hotwire集成的专业实践方案。

Core Principles

核心原则

  1. Use latest versions based on Gemfile
  2. Follow Rails conventions and best practices
  3. Use Context7 MCP or hotwire.dev for documentation
  4. Test JavaScript with RSpec system specs (Capybara + Cuprite)
  5. Review existing Stimulus controllers before creating new ones
  1. 基于Gemfile使用最新版本
  2. 遵循Rails约定和最佳实践
  3. 使用Context7 MCP或hotwire.dev作为文档参考
  4. 使用RSpec系统测试(Capybara + Cuprite)测试JavaScript
  5. 在创建新的Stimulus控制器前先审视现有控制器

Stimulus Controllers

Stimulus控制器

Guidelines

指南

  • Keep controllers simple and focused
  • Make controllers generic when possible, specific only when needed
  • Never have controllers communicate with each other
  • Integrate into ERB templates using Rails conventions
  • 保持控制器简洁且聚焦单一职责
  • 尽可能让控制器通用,仅在必要时做特定处理
  • 绝对不要让控制器之间互相通信
  • 遵循Rails约定集成到ERB模板中

Structure

结构

javascript
// app/javascript/controllers/toggle_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["content"]
  static values = { open: Boolean }

  toggle() {
    this.openValue = !this.openValue
  }

  openValueChanged() {
    this.contentTarget.classList.toggle("hidden", !this.openValue)
  }
}
javascript
// app/javascript/controllers/toggle_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["content"]
  static values = { open: Boolean }

  toggle() {
    this.openValue = !this.openValue
  }

  openValueChanged() {
    this.contentTarget.classList.toggle("hidden", !this.openValue)
  }
}

ERB Integration

ERB集成

erb
<div data-controller="toggle" data-toggle-open-value="false">
  <button data-action="toggle#toggle">Toggle</button>
  <div data-toggle-target="content" class="hidden">
    Content here
  </div>
</div>
erb
<div data-controller="toggle" data-toggle-open-value="false">
  <button data-action="toggle#toggle">Toggle</button>
  <div data-toggle-target="content" class="hidden">
    Content here
  </div>
</div>

Turbo Frames

Turbo Frames

Use for partial page updates without full refreshes.
erb
<%= turbo_frame_tag "user_profile" do %>
  <%= render @user %>
<% end %>

<!-- Link that updates only the frame -->
<%= link_to "Edit", edit_user_path(@user), data: { turbo_frame: "user_profile" } %>
用于无需全页刷新的局部页面更新。
erb
<%= turbo_frame_tag "user_profile" do %>
  <%= render @user %>
<% end %>

<!-- Link that updates only the frame -->
<%= link_to "Edit", edit_user_path(@user), data: { turbo_frame: "user_profile" } %>

Turbo Streams

Turbo Streams

Use for real-time updates from server.
ruby
undefined
用于来自服务器的实时更新。
ruby
undefined

Controller

Controller

respond_to do |format| format.turbo_stream format.html { redirect_to @post } end

```erb
<%# app/views/posts/create.turbo_stream.erb %>
<%= turbo_stream.prepend "posts", @post %>
<%= turbo_stream.update "post_count", Post.count %>
respond_to do |format| format.turbo_stream format.html { redirect_to @post } end

```erb
<%# app/views/posts/create.turbo_stream.erb %>
<%= turbo_stream.prepend "posts", @post %>
<%= turbo_stream.update "post_count", Post.count %>

AJAX Requests

AJAX请求

Use
request.js
for AJAX when needed:
javascript
import { get, post } from "@rails/request.js"

async function loadData() {
  const response = await get("/api/data", { responseKind: "json" })
  if (response.ok) {
    const data = await response.json
    // handle data
  }
}
必要时使用
request.js
处理AJAX请求:
javascript
import { get, post } from "@rails/request.js"

async function loadData() {
  const response = await get("/api/data", { responseKind: "json" })
  if (response.ok) {
    const data = await response.json
    // handle data
  }
}

Import Maps

Import Maps

Include JavaScript libraries via import maps. Only add libraries when absolutely necessary.
ruby
undefined
通过import maps引入JavaScript库。仅在绝对必要时添加库。
ruby
undefined

config/importmap.rb

config/importmap.rb


If import maps aren't used, follow whatever asset pipeline the application uses.

如果不使用import maps,则遵循应用所使用的任何资产管道。

Testing

测试

Test Hotwire features with RSpec system specs:
ruby
RSpec.describe "Posts", type: :system do
  before { driven_by(:cuprite) }

  it "updates post inline with Turbo" do
    post = posts(:published)
    visit post_path(post)

    click_link "Edit"
    fill_in "Title", with: "Updated Title"
    click_button "Save"

    expect(page).to have_content("Updated Title")
    expect(page).to have_current_path(post_path(post)) # No redirect
  end
end
使用RSpec系统测试来测试Hotwire功能:
ruby
RSpec.describe "Posts", type: :system do
  before { driven_by(:cuprite) }

  it "updates post inline with Turbo" do
    post = posts(:published)
    visit post_path(post)

    click_link "Edit"
    fill_in "Title", with: "Updated Title"
    click_button "Save"

    expect(page).to have_content("Updated Title")
    expect(page).to have_current_path(post_path(post)) # No redirect
  end
end