stimulus
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStimulus
Stimulus
Modest JavaScript framework that connects JS objects to HTML via data attributes. Stimulus does not render HTML -- it augments server-rendered HTML with behavior.
The mental model: HTML is the source of truth, JavaScript controllers attach to elements, and data attributes are the wiring. No build step required with AssetMapper.
一款轻量的JavaScript框架,通过data属性将JS对象与HTML关联起来。Stimulus不会渲染HTML——它会为服务端渲染的HTML增强交互行为。
核心心智模型:HTML是唯一数据源,JavaScript控制器绑定到元素上,data属性是连接两者的纽带。搭配AssetMapper使用时无需构建步骤。
Quick Reference
快速参考
data-controller="name" attach controller to element
data-name-target="item" mark element as a target
data-action="event->name#method" bind event to controller method
data-name-key-value="..." pass typed data to controller
data-name-key-class="..." configure CSS class names
data-name-other-outlet=".selector" reference another controller instancedata-controller="name" attach controller to element
data-name-target="item" mark element as a target
data-action="event->name#method" bind event to controller method
data-name-key-value="..." pass typed data to controller
data-name-key-class="..." configure CSS class names
data-name-other-outlet=".selector" reference another controller instanceController Skeleton
控制器骨架
javascript
// assets/controllers/example_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['input', 'output'];
static values = { url: String, delay: { type: Number, default: 300 } };
static classes = ['loading'];
static outlets = ['other'];
connect() {
// Called when controller connects to DOM
}
disconnect() {
// Called when controller disconnects -- clean up here
}
submit(event) {
// Action method
}
}File naming convention: maps to . Subdirectories use as separator: maps to .
hello_controller.jsdata-controller="hello"--components/modal_controller.jsdata-controller="components--modal"javascript
// assets/controllers/example_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['input', 'output'];
static values = { url: String, delay: { type: Number, default: 300 } };
static classes = ['loading'];
static outlets = ['other'];
connect() {
// Called when controller connects to DOM
}
disconnect() {
// Called when controller disconnects -- clean up here
}
submit(event) {
// Action method
}
}文件命名规范: 对应 。子目录使用作为分隔符: 对应 。
hello_controller.jsdata-controller="hello"--components/modal_controller.jsdata-controller="components--modal"HTML Wiring Examples
HTML绑定示例
Basic Controller
基础控制器
html
<div data-controller="hello">
<input data-hello-target="name" type="text">
<button data-action="click->hello#greet">Greet</button>
<span data-hello-target="output"></span>
</div>html
<div data-controller="hello">
<input data-hello-target="name" type="text">
<button data-action="click->hello#greet">Greet</button>
<span data-hello-target="output"></span>
</div>Values from Server (Twig)
来自服务端的值(Twig)
Pass server data to controllers via value attributes. Values are typed and automatically parsed.
html
<div data-controller="map"
data-map-latitude-value="{{ place.lat }}"
data-map-longitude-value="{{ place.lng }}"
data-map-zoom-value="12">
</div>Available types: , , , , . Values trigger callbacks when mutated.
StringNumberBooleanArrayObject{name}ValueChanged()通过value属性将服务端数据传递给控制器。值是带类型的,会自动解析。
html
<div data-controller="map"
data-map-latitude-value="{{ place.lat }}"
data-map-longitude-value="{{ place.lng }}"
data-map-zoom-value="12">
</div>可用类型:、、、、。值发生变更时会触发回调。
StringNumberBooleanArrayObject{name}ValueChanged()Actions
动作
The format is . Default events exist per element type (click for buttons, input for inputs, submit for forms) so the event can be omitted.
event->controller#methodhtml
{# Explicit event #}
<button data-action="click->hello#greet">Greet</button>
{# Default event (click for button) #}
<button data-action="hello#greet">Greet</button>
{# Multiple actions on same element #}
<input type="text"
data-action="focus->field#highlight blur->field#normalize input->field#validate">
{# Prevent default #}
<form data-action="submit->form#validate:prevent">
{# Keyboard shortcuts #}
<div data-action="keydown.esc@window->modal#close">
<input data-action="keydown.enter->modal#submit keydown.ctrl+s->modal#save">
{# Global events (window/document) #}
<div data-action="resize@window->sidebar#adjust click@document->sidebar#closeOutside">格式为。不同元素类型有默认事件(按钮对应click,输入框对应input,表单对应submit),因此可以省略事件名。
event->controller#methodhtml
{# Explicit event #}
<button data-action="click->hello#greet">Greet</button>
{# Default event (click for button) #}
<button data-action="hello#greet">Greet</button>
{# Multiple actions on same element #}
<input type="text"
data-action="focus->field#highlight blur->field#normalize input->field#validate">
{# Prevent default #}
<form data-action="submit->form#validate:prevent">
{# Keyboard shortcuts #}
<div data-action="keydown.esc@window->modal#close">
<input data-action="keydown.enter->modal#submit keydown.ctrl+s->modal#save">
{# Global events (window/document) #}
<div data-action="resize@window->sidebar#adjust click@document->sidebar#closeOutside">CSS Classes
CSS类
Externalize CSS class names so controllers stay generic:
html
<button data-controller="button"
data-button-loading-class="opacity-50 cursor-wait"
data-button-active-class="bg-blue-600"
data-action="click->button#submit">
Submit
</button>javascript
// In controller
this.element.classList.add(...this.loadingClasses);将CSS类名外置,让控制器保持通用性:
html
<button data-controller="button"
data-button-loading-class="opacity-50 cursor-wait"
data-button-active-class="bg-blue-600"
data-action="click->button#submit">
Submit
</button>javascript
// In controller
this.element.classList.add(...this.loadingClasses);Multiple Controllers
多控制器
An element can have multiple controllers:
html
<div data-controller="dropdown tooltip"
data-action="mouseenter->tooltip#show mouseleave->tooltip#hide">
<button data-action="click->dropdown#toggle">Menu</button>
<ul data-dropdown-target="menu" hidden>...</ul>
</div>一个元素可以绑定多个控制器:
html
<div data-controller="dropdown tooltip"
data-action="mouseenter->tooltip#show mouseleave->tooltip#hide">
<button data-action="click->dropdown#toggle">Menu</button>
<ul data-dropdown-target="menu" hidden>...</ul>
</div>Outlets (Cross-Controller Communication)
Outlets(跨控制器通信)
Reference other controller instances by CSS selector:
html
<div data-controller="player"
data-player-playlist-outlet="#playlist">
<button data-action="click->player#playNext">Next</button>
</div>
<ul id="playlist" data-controller="playlist">
<li data-playlist-target="track">Song 1</li>
<li data-playlist-target="track">Song 2</li>
</ul>javascript
// In player controller
static outlets = ['playlist'];
playNext() {
const tracks = this.playlistOutlet.trackTargets;
// ...
}通过CSS选择器引用其他控制器实例:
html
<div data-controller="player"
data-player-playlist-outlet="#playlist">
<button data-action="click->player#playNext">Next</button>
</div>
<ul id="playlist" data-controller="playlist">
<li data-playlist-target="track">Song 1</li>
<li data-playlist-target="track">Song 2</li>
</ul>javascript
// In player controller
static outlets = ['playlist'];
playNext() {
const tracks = this.playlistOutlet.trackTargets;
// ...
}Lazy Loading (Heavy Dependencies)
懒加载(重依赖场景)
Load controller JS only when the element appears in the viewport. Use for controllers with heavy dependencies (chart libs, editors, maps).
javascript
/* stimulusFetch: 'lazy' */
import { Controller } from '@hotwired/stimulus';
import Chart from 'chart.js';
export default class extends Controller {
connect() {
// Chart.js is only loaded when this element enters the viewport
}
}The comment must be the very first line of the file.
/* stimulusFetch: 'lazy' */仅当元素出现在视口中时才加载控制器JS。适用于依赖较重的控制器(图表库、编辑器、地图)。
javascript
/* stimulusFetch: 'lazy' */
import { Controller } from '@hotwired/stimulus';
import Chart from 'chart.js';
export default class extends Controller {
connect() {
// Chart.js is only loaded when this element enters the viewport
}
}/* stimulusFetch: 'lazy' */Symfony / Twig Integration
Symfony / Twig 集成
Raw data attributes are the recommended approach -- they work everywhere, are easy to read, and need no special helpers.
twig
{# Raw attributes (preferred) #}
<div data-controller="search"
data-search-url-value="{{ path('api_search') }}">Twig helpers exist for complex cases or when generating attributes programmatically:
twig
{# Twig helper #}
<div {{ stimulus_controller('search', { url: path('api_search') }) }}>
{# Chaining multiple controllers #}
<div {{ stimulus_controller('a')|stimulus_controller('b') }}>
{# Target and action helpers #}
<input {{ stimulus_target('search', 'query') }}>
<button {{ stimulus_action('search', 'submit') }}>推荐直接使用原生的data属性——它们随处可用、可读性强,无需特殊的辅助函数。
twig
{# Raw attributes (preferred) #}
<div data-controller="search"
data-search-url-value="{{ path('api_search') }}">复杂场景或需要动态生成属性时可以使用Twig辅助函数:
twig
{# Twig helper #}
<div {{ stimulus_controller('search', { url: path('api_search') }) }}>
{# Chaining multiple controllers #}
<div {{ stimulus_controller('a')|stimulus_controller('b') }}>
{# Target and action helpers #}
<input {{ stimulus_target('search', 'query') }}>
<button {{ stimulus_action('search', 'submit') }}>Key Principles
核心原则
HTML drives, JS responds. Controllers don't create markup -- they attach behavior to existing HTML. If you find yourself generating DOM in a controller, consider whether a TwigComponent or LiveComponent would be better.
One controller, one concern. A dropdown controller handles dropdowns. A tooltip controller handles tooltips. Compose multiple controllers on the same element rather than building mega-controllers.
Clean up in disconnect(). If adds event listeners, timers, or third-party library instances, must remove them. Turbo navigation will disconnect and reconnect controllers as pages change.
connect()disconnect()Values over data attributes. Use Stimulus values (typed, with change callbacks) rather than raw attributes for data that the controller needs to read or watch.
data-*HTML驱动,JS响应。 控制器不会生成标记——它们只为已有的HTML添加行为。如果你发现自己需要在控制器中生成DOM,可以考虑是否用TwigComponent或LiveComponent更合适。
一个控制器只负责一个功能。 下拉控制器只处理下拉逻辑,提示框控制器只处理提示框逻辑。在同一个元素上组合多个控制器,而不是构建巨型控制器。
在disconnect()中清理资源。 如果中添加了事件监听器、定时器或者第三方库实例,中必须移除它们。Turbo导航在页面切换时会断开并重新连接控制器。
connect()disconnect()优先使用值而非原始data属性。 对于控制器需要读取或监听的数据,使用Stimulus值(带类型,支持变更回调),而不是原生的属性。
data-*References
参考资料
- Full API (lifecycle, targets, values, actions, classes, outlets): references/api.md
- Patterns (debounce, fetch, modals, forms, etc.): references/patterns.md
- Gotchas (common mistakes, debugging, Turbo compatibility): references/gotchas.md
- 完整API(生命周期、targets、values、actions、classes、outlets):references/api.md
- 实践模式(防抖、fetch、模态框、表单等):references/patterns.md
- 注意事项(常见错误、调试、Turbo兼容性):references/gotchas.md