better-stimulus

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Better Stimulus

Better Stimulus 最佳实践

Apply opinionated best practices from betterstimulus.com when writing or refactoring Stimulus controllers. These patterns emphasize code reusability, proper separation of concerns, and SOLID design principles.
在编写或重构Stimulus控制器时,应用来自betterstimulus.com的约定式最佳实践。这些模式强调代码复用性、关注点合理分离以及SOLID设计原则。

When to Use This Skill

何时使用此技能

Invoke this skill when:
  • Writing new Stimulus controllers
  • Refactoring existing Stimulus code
  • Reviewing Stimulus controller architecture
  • Debugging inter-controller communication
  • Integrating third-party JavaScript libraries
  • Implementing form submission logic
  • Managing controller state
  • Setting up Turbo integration
在以下场景中调用此技能:
  • 编写新的Stimulus控制器
  • 重构现有Stimulus代码
  • 评审Stimulus控制器架构
  • 调试控制器间通信
  • 集成第三方JavaScript库
  • 实现表单提交逻辑
  • 管理控制器状态
  • 设置Turbo集成

Core Principles

核心原则

1. Make Controllers Configurable

1. 让控制器可配置

Externalize hardcoded values into data attributes rather than embedding them in controller logic.
Bad:
javascript
toggle() {
  this.element.classList.toggle("active")
}
Good:
javascript
static classes = ["active"]
toggle() {
  this.element.classList.toggle(this.activeClass)
}
html
<div data-controller="toggle" data-toggle-active-class="active"></div>
将硬编码值提取到data属性中,而非嵌入到控制器逻辑里。
不良示例:
javascript
toggle() {
  this.element.classList.toggle("active")
}
良好示例:
javascript
static classes = ["active"]
toggle() {
  this.element.classList.toggle(this.activeClass)
}
html
<div data-controller="toggle" data-toggle-active-class="active"></div>

2. Use Values API for State

2. 使用Values API管理状态

Store controller state in Stimulus values, not instance properties, to leverage reactivity and DOM persistence.
Bad:
javascript
connect() {
  this.count = 0
}
Good:
javascript
static values = { count: Number }
countValueChanged(count) {
  this.updateDisplay()
}
将控制器状态存储在Stimulus values中,而非实例属性,以利用响应式特性和DOM持久化能力。
不良示例:
javascript
connect() {
  this.count = 0
}
良好示例:
javascript
static values = { count: Number }
countValueChanged(count) {
  this.updateDisplay()
}

3. Keep Controllers Focused (Single Responsibility)

3. 保持控制器聚焦(单一职责)

Each controller should have one reason to change. Split controllers that mix concerns.
Ask: "What would cause this controller to change?" If multiple unrelated reasons, split it.
每个控制器应该只有一个变更理由。拆分混合了多个关注点的控制器。
自问:"什么会导致这个控制器变更?"如果有多个不相关的理由,就拆分它。

4. Don't Overuse connect()

4. 不要过度使用connect()

Use
connect()
for:
  • Instantiating third-party plugins (Swiper, Chart.js, etc.)
  • Feature detection/browser capabilities
Don't use
connect()
for:
  • Setting up state (use Values API)
  • Adding event listeners (use
    data-action
    )
connect()
适用于:
  • 实例化第三方插件(Swiper、Chart.js等)
  • 特性检测/浏览器能力判断
connect()
不适用于:
  • 设置状态(使用Values API)
  • 添加事件监听器(使用
    data-action

5. Register Events Declaratively

5. 声明式注册事件

Use
data-action
attributes
instead of
addEventListener()
to let Stimulus manage lifecycle.
Bad:
javascript
connect() {
  document.addEventListener("click", this.handler.bind(this))
}
Good:
html
<div data-action="click@document->controller#handler"></div>
使用
data-action
属性
而非
addEventListener()
,让Stimulus管理生命周期。
不良示例:
javascript
connect() {
  document.addEventListener("click", this.handler.bind(this))
}
良好示例:
html
<div data-action="click@document->controller#handler"></div>

Key Patterns

关键模式

Architecture

架构

  • Configurable Controllers: Inject dependencies via data attributes
  • Application Controller: Base class for shared functionality
  • Mixins: Share behavior via "acts as" relationships
  • Targetless Controllers: Separate element vs. target manipulation
  • Namespaced Attributes: Handle arbitrary parameter sets
See:
references/architecture.md
  • 可配置控制器:通过data属性注入依赖
  • 应用控制器:共享功能的基类
  • 混合(Mixins):通过"行为类似"关系共享行为
  • 无目标控制器:区分元素与目标操作
  • 命名空间属性:处理任意参数集合
参考:
references/architecture.md

State Management

状态管理

  • Use Values API for nearly all state
  • Leverage change callbacks (
    [name]ValueChanged
    )
  • Keep values serializable
  • Provide sensible defaults
See:
references/state-management.md
  • 几乎所有状态都使用Values API
  • 利用变更回调(
    [name]ValueChanged
  • 保持值可序列化
  • 提供合理的默认值
参考:
references/state-management.md

Lifecycle

生命周期

  • Use
    connect()
    for third-party library initialization
  • Pair
    connect()
    with
    disconnect()
    for cleanup
  • Avoid overloading
    connect()
    with state setup
  • Implement
    teardown()
    for Turbo-specific cleanup
See:
references/lifecycle.md
  • 使用
    connect()
    初始化第三方库
  • 配对使用
    connect()
    disconnect()
    进行清理
  • 避免在
    connect()
    中过度处理状态设置
  • 为Turbo特定清理实现
    teardown()
参考:
references/lifecycle.md

Controller Communication

控制器通信

Three approaches:
  1. Custom Events: Loose coupling, broadcast pattern
  2. Outlets: Direct controller references, structured layouts
  3. Callbacks: Request state from other controllers
Choose based on relationship:
  • Unknown receivers → Custom events
  • Known hierarchy → Outlets
  • Data sharing → Callbacks
See:
references/events-and-interaction.md
三种方式:
  1. 自定义事件:松耦合,广播模式
  2. 出口(Outlets):直接控制器引用,结构化布局
  3. 回调:向其他控制器请求状态
根据关系选择:
  • 未知接收者 → 自定义事件
  • 已知层级结构 → 出口
  • 数据共享 → 回调
参考:
references/events-and-interaction.md

SOLID Principles

SOLID原则

  • Single Responsibility: One reason to change
  • Open-Closed: Extend via inheritance, not modification
  • Dependency Inversion: Depend on abstractions, inject via config
See:
references/solid-principles.md
  • 单一职责:只有一个变更理由
  • 开闭原则:通过继承扩展,而非修改
  • 依赖倒置:依赖抽象,通过配置注入
参考:
references/solid-principles.md

DOM & Turbo

DOM与Turbo

  • Use
    <template>
    to restore DOM state
  • Use
    requestSubmit()
    not
    submit()
    for forms
  • Implement global teardown for Turbo caching
  • Handle Turbo events declaratively
See:
references/dom-and-turbo.md
  • 使用
    <template>
    恢复DOM状态
  • 表单提交使用
    requestSubmit()
    而非
    submit()
  • 为Turbo缓存实现全局清理
  • 声明式处理Turbo事件
参考:
references/dom-and-turbo.md

Error Handling

错误处理

  • Create ApplicationController with
    handleError()
    method
  • Integrate with error tracking (Sentry, Honeybadger)
  • Provide user-friendly messages
  • Use try-catch for async operations
See:
references/error-handling.md
  • 创建带有
    handleError()
    方法的ApplicationController
  • 集成错误跟踪工具(Sentry、Honeybadger)
  • 提供用户友好的提示信息
  • 异步操作使用try-catch包裹
参考:
references/error-handling.md

Quick Reference

快速参考

Value Types

值类型

javascript
static values = {
  url: String,
  count: Number,
  enabled: Boolean,
  items: Array,
  config: Object
}
javascript
static values = {
  url: String,
  count: Number,
  enabled: Boolean,
  items: Array,
  config: Object
}

Event Actions

事件动作

html
<!-- Element events -->
<div data-action="click->controller#method">

<!-- Global events -->
<div data-action="resize@window->controller#layout">
<div data-action="keydown@document->controller#handleKey">

<!-- Multiple actions -->
<div data-action="click->ctrl1#method1 click->ctrl2#method2">
html
<!-- 元素事件 -->
<div data-action="click->controller#method">

<!-- 全局事件 -->
<div data-action="resize@window->controller#layout">
<div data-action="keydown@document->controller#handleKey">

<!-- 多个动作 -->
<div data-action="click->ctrl1#method1 click->ctrl2#method2">

Custom Events

自定义事件

javascript
// Dispatch
const event = new CustomEvent('name:action', {
  bubbles: true,
  detail: { key: 'value' }
})
this.element.dispatchEvent(event)

// Listen
data-action="name:action->controller#handler"
javascript
// 派发
const event = new CustomEvent('name:action', {
  bubbles: true,
  detail: { key: 'value' }
})
this.element.dispatchEvent(event)

// 监听
data-action="name:action->controller#handler"

Outlets

出口(Outlets)

html
<div data-controller="parent"
     data-parent-child-outlet=".child">
  <div class="child" data-controller="child"></div>
</div>
javascript
static outlets = ['child']
this.childOutlets.forEach(outlet => outlet.method())
html
<div data-controller="parent"
     data-parent-child-outlet=".child">
  <div class="child" data-controller="child"></div>
</div>
javascript
static outlets = ['child']
this.childOutlets.forEach(outlet => outlet.method())

Lifecycle Hooks

生命周期钩子

javascript
connect()           // Element connected to DOM
disconnect()        // Element removed from DOM
[name]TargetConnected(element)      // Target added
[name]TargetDisconnected(element)   // Target removed
[name]ValueChanged(value, oldValue) // Value changed
[name]OutletConnected(outlet)       // Outlet connected
[name]OutletDisconnected(outlet)    // Outlet disconnected
javascript
connect()           // 元素连接到DOM
disconnect()        // 元素从DOM移除
[name]TargetConnected(element)      // 目标添加
[name]TargetDisconnected(element)   // 目标移除
[name]ValueChanged(value, oldValue) // 值变更
[name]OutletConnected(outlet)       // 出口连接
[name]OutletDisconnected(outlet)    // 出口断开

Implementation Workflow

实施流程

When writing a new controller:
  1. Identify responsibility - What single purpose does this serve?
  2. Choose state approach - Use Values API unless non-serializable
  3. Declare static properties - values, targets, classes, outlets
  4. Implement change callbacks - React to value changes
  5. Keep connect() minimal - Only for third-party setup
  6. Use declarative actions - Avoid addEventListener
  7. Handle errors gracefully - Wrap risky operations in try-catch
  8. Test lifecycle - Verify connect/disconnect behavior
When refactoring:
  1. Check Single Responsibility - Split if multiple concerns
  2. Extract configuration - Move hardcoded values to data attributes
  3. Convert to Values API - Replace instance properties with values
  4. Simplify connect() - Move state and listeners out
  5. Use inheritance/mixins - Share common behavior properly
  6. Decouple controllers - Use events/outlets for communication
  7. Add error handling - Implement handleError from ApplicationController
编写新控制器时:
  1. 明确职责 - 这个控制器的单一用途是什么?
  2. 选择状态方案 - 除非是非序列化状态,否则使用Values API
  3. 声明静态属性 - values、targets、classes、outlets
  4. 实现变更回调 - 响应值变更
  5. 精简connect() - 仅用于第三方库初始化
  6. 使用声明式动作 - 避免使用addEventListener
  7. 优雅处理错误 - 用try-catch包裹风险操作
  8. 测试生命周期 - 验证connect/disconnect行为
重构时:
  1. 检查单一职责 - 如果有多个关注点则拆分
  2. 提取配置 - 将硬编码值移至data属性
  3. 转换为Values API - 用values替换实例属性
  4. 简化connect() - 移出状态设置和监听器
  5. 使用继承/混合 - 正确共享通用行为
  6. 解耦控制器 - 使用事件/出口进行通信
  7. 添加错误处理 - 实现ApplicationController中的handleError
  8. 验证生命周期 - 确保清理逻辑正常工作

Common Mistakes to Avoid

需避免的常见错误

  • ❌ Hardcoding CSS classes, selectors, or IDs in controllers
  • ❌ Using instance properties for state instead of values
  • ❌ Overloading
    connect()
    with state setup and event listeners
  • ❌ Creating "page controllers" that handle multiple concerns
  • ❌ Using
    addEventListener()
    without proper cleanup
  • ❌ Calling
    .bind()
    separately in connect and disconnect
  • ❌ Using
    submit()
    instead of
    requestSubmit()
  • ❌ Modifying base classes instead of extending them
  • ❌ Tight coupling between controllers
  • ❌ Swallowing errors without logging or reporting
  • ❌ 在控制器中硬编码CSS类、选择器或ID
  • ❌ 使用实例属性存储状态而非values
  • ❌ 在
    connect()
    中过度处理状态设置和事件监听器
  • ❌ 创建处理多个关注点的"页面控制器"
  • ❌ 使用
    addEventListener()
    但未正确清理
  • ❌ 在connect和disconnect中分别调用
    .bind()
  • ❌ 使用
    submit()
    而非
    requestSubmit()
  • ❌ 修改基类而非扩展它们
  • ❌ 控制器间紧耦合
  • ❌ 吞掉错误而不记录或上报

Resources

资源

All patterns in this skill come from betterstimulus.com, an opinionated collection of StimulusJS best practices.
For detailed explanations and examples, see:
  • references/architecture.md
    - Controller design patterns
  • references/state-management.md
    - Values API usage
  • references/lifecycle.md
    - Lifecycle best practices
  • references/events-and-interaction.md
    - Communication patterns
  • references/solid-principles.md
    - SOLID design principles
  • references/dom-and-turbo.md
    - DOM manipulation and Turbo
  • references/error-handling.md
    - Error management
此技能中的所有模式均来自betterstimulus.com,这是一个StimulusJS最佳实践的约定式合集。
如需详细说明和示例,请查看:
  • references/architecture.md
    - 控制器设计模式
  • references/state-management.md
    - Values API使用方法
  • references/lifecycle.md
    - 生命周期最佳实践
  • references/events-and-interaction.md
    - 通信模式
  • references/solid-principles.md
    - SOLID设计原则
  • references/dom-and-turbo.md
    - DOM操作与Turbo
  • references/error-handling.md
    - 错误管理