mcpField
B
better-stimulus

better-stimulus

github.com/obie开发10168 次安装1 周前更新
代码仓库

关于 better-stimulus

better-stimulus 是一项为编写、重构和审查 Stimulus 控制器提供指导的技能,面向前端开发者。它汇集可配置控制器、状态管理、生命周期和控制器通信等实践。

主要功能

  1. 01通过 data 属性配置控制器
  2. 02使用 Values API 管理状态
  3. 03遵循单一职责原则
  4. 04以声明式方式注册事件
  5. 05处理控制器通信与 Turbo 生命周期

适用场景

  1. 01编写新的 Stimulus 控制器
  2. 02拆分并重构多职责控制器
  3. 03排查控制器间通信问题
  4. 04集成第三方 JavaScript 库或 Turbo

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/obie/skills --skill better-stimulus

安装后在 Claude Code 中输入/better-stimulus确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
architecture.md
dom-and-turbo.md
error-handling.md
events-and-interaction.md
lifecycle.md
solid-principles.md
state-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

控制器通信

有三种方式:

  1. 自定义事件:松散耦合、广播模式
  2. Outlets:直接引用控制器、适用于结构化布局
  3. 回调:从其他控制器请求状态

根据关系选择:

  • 接收方未知 → 自定义事件
  • 层级关系明确 → 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

实现工作流

编写新控制器时:

  1. 明确职责 - 它的单一用途是什么?
  2. 选择状态方案 - 除非状态不可序列化,否则使用 Values API
  3. 声明静态属性 - values、targets、classes、outlets
  4. 实现变更回调 - 响应值的变化
  5. 保持 connect() 简洁 - 仅用于第三方设置
  6. 使用声明式操作 - 避免使用 addEventListener
  7. 妥善处理错误 - 使用 try-catch 包裹有风险的操作
  8. 测试生命周期 - 验证 connect/disconnect 行为

重构时:

  1. 检查单一职责 - 如果涉及多个关注点,则拆分
  2. 提取配置 - 将硬编码值移至数据属性
  3. 转换为 Values API - 用 values 替换实例属性
  4. 简化 connect() - 将状态和监听器移出
  5. 使用继承/混入 - 妥善共享通用行为
  6. 解耦控制器 - 使用事件/outlets 进行通信
  7. 添加错误处理 - 实现 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 操作与 Turbo
  • references/error-handling.md - 错误管理

评论

支持 Markdown