关于 better-stimulus
better-stimulus 是一项为编写、重构和审查 Stimulus 控制器提供指导的技能,面向前端开发者。它汇集可配置控制器、状态管理、生命周期和控制器通信等实践。
主要功能
- 01通过 data 属性配置控制器
- 02使用 Values API 管理状态
- 03遵循单一职责原则
- 04以声明式方式注册事件
- 05处理控制器通信与 Turbo 生命周期
适用场景
- 01编写新的 Stimulus 控制器
- 02拆分并重构多职责控制器
- 03排查控制器间通信问题
- 04集成第三方 JavaScript 库或 Turbo
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ obie/ skills --skill better-stimulus安装后在 Claude Code 中输入/better-stimulus确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/architecture.mddom-and-turbo.mderror-handling.mdevents-and-interaction.mdlifecycle.mdsolid-principles.mdstate-management.md详细介绍
更好的 Stimulus
在编写或重构 Stimulus 控制器时,应用 betterstimulus.com 中固守的最佳实践。这些模式强调代码复用、合理分离关注点和 SOLID 设计原则。
何时使用此技能
在以下情况下调用此技能:
- 编写新的 Stimulus 控制器
- 重构现有 Stimulus 代码
- 审查 Stimulus 控制器架构
- 调试控制器间通信
- 集成第三方 JavaScript 库
- 实现表单提交逻辑
- 管理控制器状态
- 设置 Turbo 集成
核心原则
1. 让控制器可配置
将硬编码的值移到数据属性中,而不是将其嵌入控制器逻辑。
错误示例:
toggle() {
this.element.classList.toggle("active")
}
正确示例:
static classes = ["active"]
toggle() {
this.element.classList.toggle(this.activeClass)
}
<div data-controller="toggle" data-toggle-active-class="active"></div>
2. 使用 Values API 管理状态
将控制器状态存储在 Stimulus values 中,而不是实例属性中,以利用响应性和 DOM 持久化。
错误示例:
connect() {
this.count = 0
}
正确示例:
static values = { count: Number }
countValueChanged(count) {
this.updateDisplay()
}
3. 保持控制器职责专一(单一职责)
每个控制器都应该只有一个变更原因。 拆分混合多个关注点的控制器。
可以问:“什么情况会导致这个控制器发生变更?”如果有多个无关原因,就拆分它。
4. 不要过度使用 connect()
使用 connect() 来:
- 实例化第三方插件(Swiper、Chart.js 等)
- 检测功能或浏览器能力
不要使用 connect() 来:
- 设置状态(使用 Values API)
- 添加事件监听器(使用
data-action)
5. 以声明方式注册事件
使用 data-action 属性,而不是 addEventListener(),让 Stimulus 管理生命周期。
错误示例:
connect() {
document.addEventListener("click", this.handler.bind(this))
}
正确示例:
<div data-action="click@document->controller#handler"></div>
关键模式
架构
- 可配置控制器:通过数据属性注入依赖项
- Application 控制器:用于共享功能的基类
- 混入:通过“acts as”关系共享行为
- 无 Target 控制器:分离元素操作与 Target 操作
- 命名空间属性:处理任意参数集
参见:references/architecture.md
状态管理
- 几乎所有状态都使用 Values API
- 利用变更回调(
[name]ValueChanged) - 确保 values 可序列化
- 提供合理的默认值
参见:references/state-management.md
生命周期
- 使用
connect()初始化第三方库 - 将
connect()与disconnect()配对进行清理 - 避免在
connect()中过度处理状态设置 - 实现
teardown(),以执行 Turbo 专用清理
参见:references/lifecycle.md
控制器通信
有三种方式:
- 自定义事件:松散耦合、广播模式
- Outlets:直接引用控制器、适用于结构化布局
- 回调:从其他控制器请求状态
根据关系选择:
- 接收方未知 → 自定义事件
- 层级关系明确 → Outlets
- 共享数据 → 回调
参见:references/events-and-interaction.md
SOLID 原则
- 单一职责:只有一个变更原因
- 开闭原则:通过继承扩展,而非修改
- 依赖倒置:依赖抽象,通过配置注入
参见:references/solid-principles.md
DOM 与 Turbo
- 使用
<template>恢复 DOM 状态 - 表单使用
requestSubmit(),而非submit() - 实现全局 teardown,以支持 Turbo 缓存
- 以声明方式处理 Turbo 事件
参见:references/dom-and-turbo.md
错误处理
- 创建包含
handleError()方法的 ApplicationController - 集成错误跟踪工具(Sentry、Honeybadger)
- 提供对用户友好的消息
- 对异步操作使用 try-catch
参见:references/error-handling.md
快速参考
Value 类型
static values = {
url: String,
count: Number,
enabled: Boolean,
items: Array,
config: Object
}
事件操作
<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">
自定义事件
// Dispatch
const event = new CustomEvent('name:action', {
bubbles: true,
detail: { key: 'value' }
})
this.element.dispatchEvent(event)
// Listen
data-action="name:action->controller#handler"
Outlets
<div data-controller="parent"
data-parent-child-outlet=".child">
<div class="child" data-controller="child"></div>
</div>
static outlets = ['child']
this.childOutlets.forEach(outlet => outlet.method())
生命周期钩子
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
实现工作流
编写新控制器时:
- 明确职责 - 它的单一用途是什么?
- 选择状态方案 - 除非状态不可序列化,否则使用 Values API
- 声明静态属性 - values、targets、classes、outlets
- 实现变更回调 - 响应值的变化
- 保持 connect() 简洁 - 仅用于第三方设置
- 使用声明式操作 - 避免使用 addEventListener
- 妥善处理错误 - 使用 try-catch 包裹有风险的操作
- 测试生命周期 - 验证 connect/disconnect 行为
重构时:
- 检查单一职责 - 如果涉及多个关注点,则拆分
- 提取配置 - 将硬编码值移至数据属性
- 转换为 Values API - 用 values 替换实例属性
- 简化 connect() - 将状态和监听器移出
- 使用继承/混入 - 妥善共享通用行为
- 解耦控制器 - 使用事件/outlets 进行通信
- 添加错误处理 - 实现 ApplicationController 中的 handleError
应避免的常见错误
- ❌ 在控制器中硬编码 CSS 类、选择器或 ID
- ❌ 使用实例属性存储状态,而不是使用 values
- ❌ 在
connect()中堆积状态设置和事件监听器 - ❌ 创建处理多个关注点的“页面控制器”
- ❌ 使用
addEventListener()却未妥善清理 - ❌ 在 connect 和 disconnect 中分别调用
.bind() - ❌ 使用
submit()而不是requestSubmit() - ❌ 修改基类,而不是扩展它们
- ❌ 控制器之间紧密耦合
- ❌ 吞掉错误,却不记录或报告
资源
本技能中的所有模式均来自 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 操作与 Turboreferences/error-handling.md- 错误管理