hotwire
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHotwire, 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
核心原则
- Use latest versions based on Gemfile
- Follow Rails conventions and best practices
- Use Context7 MCP or hotwire.dev for documentation
- Test JavaScript with RSpec system specs (Capybara + Cuprite)
- Review existing Stimulus controllers before creating new ones
- 基于Gemfile使用最新版本
- 遵循Rails约定和最佳实践
- 使用Context7 MCP或hotwire.dev作为文档参考
- 使用RSpec系统测试(Capybara + Cuprite)测试JavaScript
- 在创建新的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
undefinedController
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 for AJAX when needed:
request.jsjavascript
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
}
}必要时使用处理AJAX请求:
request.jsjavascript
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
undefinedconfig/importmap.rb
config/importmap.rb
pin "lodash", to: "https://ga.jspm.io/npm:lodash@4.17.21/lodash.js"
If import maps aren't used, follow whatever asset pipeline the application uses.pin "lodash", to: "https://ga.jspm.io/npm:lodash@4.17.21/lodash.js"
如果不使用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