用途
Better Stimulus 用于在编写或重构 StimulusJS 控制器时应用一套有明确主张的最佳实践。这些模式强调代码可复用性、恰当的关注点分离以及 SOLID 设计原则。
适用时机:编写新的 Stimulus 控制器、重构现有 Stimulus 代码、审查 Stimulus 控制器架构、调试控制器间通信、集成第三方 JavaScript 库、实现表单提交逻辑、管理控制器状态、配置 Turbo 集成。
核心原则
-
让控制器可配置:将硬编码值外置为 data 属性,而非嵌入控制器逻辑。例如使用
static classes = ["active"]并通过this.activeClass引用,配合data-toggle-active-class="active"。 -
使用 Values API 管理状态:将状态存储在 Stimulus values 中,而非实例属性,以利用响应式和 DOM 持久化。例如
static values = { count: Number }并实现countValueChanged(count)。 -
保持控制器专注(单一职责):每个控制器应只有一个变更理由。若存在多个不相关的原因,应拆分。
-
不要过度使用 connect():connect() 应用于实例化第三方插件(如 Swiper、Chart.js)以及特性检测/浏览器能力判断;不应用于设置状态(改用 Values API)或添加事件监听器(改用 data-action)。
-
以声明式方式注册事件:使用 data-action 属性而非 addEventListener(),让 Stimulus 管理生命周期。例如
data-action="click@document->controller#handler"。
关键模式
架构:可配置控制器(通过 data 属性注入依赖)、Application Controller(共享功能的基类)、Mixins(通过“acts as”关系共享行为)、无目标控制器(分离元素与目标操作)、命名空间属性(处理任意参数集)。详见 references/architecture.md。
状态管理:几乎所有状态都使用 Values API,利用变更回调([name]ValueChanged),保持值可序列化,提供合理默认值。详见 references/state-management.md。
生命周期:使用 connect() 初始化第三方库,并与 disconnect() 配对进行清理,避免在 connect() 中重载状态设置,为 Turbo 特定清理实现 teardown()。详见 references/lifecycle.md。
控制器通信:三种方式——自定义事件(松耦合、广播模式)、Outlets(直接控制器引用、结构化布局)、回调(向其他控制器请求状态)。选择依据:未知接收方用自定义事件,已知层级用 Outlets,数据共享用回调。详见 references/events-and-interaction.md。
SOLID 原则:单一职责(一个变更理由)、开闭原则(通过继承扩展而非修改)、依赖倒置(依赖抽象,通过配置注入)。详见 references/solid-principles.md。
DOM 与 Turbo:使用 <template> 恢复 DOM 状态,表单使用 requestSubmit() 而非 submit(),为 Turbo 缓存实现全局 teardown,以声明式方式处理 Turbo 事件。详见 references/dom-and-turbo.md。
错误处理:创建带 handleError() 方法的 ApplicationController,集成错误跟踪(Sentry、Honeybadger),提供用户友好的消息,对异步操作使用 try-catch。详见 references/error-handling.md。
快速参考
值类型:static values = { url: String, count: Number, enabled: Boolean, items: Array, config: Object }
事件动作:元素事件 data-action="click->controller#method";全局事件 data-action="resize@window->controller#layout"、data-action="keydown@document->controller#handleKey";多动作 data-action="click->ctrl1#method1 click->ctrl2#method2"。
自定义事件:派发 const event = new CustomEvent('name:action', { bubbles: true, detail: { key: 'value' } }); this.element.dispatchEvent(event);监听 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()、disconnect()、[name]TargetConnected(element)、[name]TargetDisconnected(element)、[name]ValueChanged(value, oldValue)、[name]OutletConnected(outlet)、[name]OutletDisconnected(outlet)。
实现工作流
编写新控制器时:识别职责(单一用途是什么)、选择状态方案(除非不可序列化,否则用 Values API)、声明静态属性(values、targets、classes、outlets)、实现变更回调、保持 connect() 最小化(仅用于第三方设置)、使用声明式动作(避免 addEventListener)、优雅处理错误(对风险操作包裹 try-catch)、测试生命周期(验证 connect/disconnect 行为)。
重构时:检查单一职责(多关注点则拆分)、提取配置(将硬编码值移至 data 属性)、转换为 Values API(用 values 替换实例属性)、简化 connect()(移出状态和监听器)、使用继承/mixins(恰当共享通用行为)、解耦控制器(用事件/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 操作与 Turbo)、references/error-handling.md(错误管理)。