管理状态
大多数当代框架鼓励你始终在 JavaScript 中保留状态。它们将 DOM 视为只写渲染目标,由消耗服务器 JSON 的客户端模板进行协调。
Stimulus 采取了不同的方法。Stimulus 应用程序的状态作为 DOM 中的属性存在;控制器本身在很大程度上是无状态的。这种方法使得可以从任何地方使用 HTML(初始文档、Ajax 请求、Turbo 访问,甚至另一个 JavaScript 库),并且在没有任何显式初始化步骤的情况下自动使关联的控制器生效。
﹟ 构建幻灯片
在上一章中,我们学习了 Stimulus 控制器如何通过向元素添加类名来维护文档中的简单状态。但是,当我们需要存储值(而不仅仅是一个简单标记)时,我们该怎么办?
我们将通过构建一个幻灯片控制器来研究这个问题,该控制器将其当前选定的幻灯片索引保存在属性中。
和往常一样,我们从 HTML 开始
<div data-controller="slideshow">
<button data-action="slideshow#previous"> ← </button>
<button data-action="slideshow#next"> → </button>
<div data-slideshow-target="slide">🐵</div>
<div data-slideshow-target="slide">🙈</div>
<div data-slideshow-target="slide">🙉</div>
<div data-slideshow-target="slide">🙊</div>
</div>
每个 slide
目标表示幻灯片中的单个幻灯片。我们的控制器将负责确保一次只显示一张幻灯片。
让我们起草我们的控制器。创建一个新文件 src/controllers/slideshow_controller.js
,如下所示
// src/controllers/slideshow_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "slide" ]
initialize() {
this.index = 0
this.showCurrentSlide()
}
next() {
this.index++
this.showCurrentSlide()
}
previous() {
this.index--
this.showCurrentSlide()
}
showCurrentSlide() {
this.slideTargets.forEach((element, index) => {
element.hidden = index !== this.index
})
}
}
我们的控制器定义了一个方法 showCurrentSlide()
,该方法遍历每个幻灯片目标,如果其索引匹配,则切换 hidden
属性。
我们通过显示第一张幻灯片来初始化控制器,并且 next()
和 previous()
动作方法会推进和倒回当前幻灯片。
﹟ 生命周期回调说明
initialize()
方法做什么?它与我们之前使用的connect()
方法有何不同?这些是 Stimulus 生命周期回调方法,当你的控制器进入或离开文档时,它们可用于设置或清除关联的状态。
方法 由 Stimulus 调用… initialize() 首先,在控制器首次实例化时 connect() 控制器随时连接到 DOM disconnect() 控制器随时从 DOM 断开连接
重新加载页面并确认“下一步”按钮可进入下一张幻灯片。
﹟ 从 DOM 读取初始状态
请注意我们的控制器如何跟踪其状态(当前选定的幻灯片)——在 this.index
属性中。
现在假设我们希望以第二张幻灯片(而不是第一张)开始我们的某个幻灯片放映。我们如何在标记中编码开始索引?
一种方法可能是使用 HTML data
属性加载初始索引。例如,我们可以向控制器的元素添加 data-index
属性
<div data-controller="slideshow" data-index="1">
然后,在我们的 initialize()
方法中,我们可以读取该属性,将其转换为整数,并将其传递给 showCurrentSlide()
initialize() {
this.index = Number(this.element.dataset.index)
this.showCurrentSlide()
}
这可能完成任务,但它很笨拙,要求我们决定如何命名属性,并且如果我们希望再次访问索引或增加索引并在 DOM 中保留结果,它对我们没有帮助。
﹟ 使用值
Stimulus 控制器支持自动映射到数据属性的类型值属性。当我们在控制器类的顶部添加值定义时
static values = { index: Number }
Stimulus 将创建一个与 data-slideshow-index-value
属性关联的 this.indexValue
控制器属性,并为我们处理数字转换。
让我们看看它的实际操作。向我们的 HTML 添加关联的数据属性
<div data-controller="slideshow" data-slideshow-index-value="1">
然后向控制器添加 static values
定义,并将 initialize()
方法更改为记录 this.indexValue
export default class extends Controller {
static values = { index: Number }
initialize() {
console.log(this.indexValue)
console.log(typeof this.indexValue)
}
// …
}
重新加载页面并验证控制台显示 1
和 Number
。
﹟
static values
行是怎么回事?与目标类似,你通过在名为
values
的静态对象属性中描述值来定义 Stimulus 控制器中的值。在本例中,我们定义了一个名为index
的单一数字值。你可以在 参考文档 中阅读有关值定义的更多信息。
现在,让我们更新控制器中的 initialize()
和其他方法,以使用 this.indexValue
而不是 this.index
。当我们完成后,控制器应如下所示
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "slide" ]
static values = { index: Number }
initialize() {
this.showCurrentSlide()
}
next() {
this.indexValue++
this.showCurrentSlide()
}
previous() {
this.indexValue--
this.showCurrentSlide()
}
showCurrentSlide() {
this.slideTargets.forEach((element, index) => {
element.hidden = index !== this.indexValue
})
}
}
重新加载页面并使用 Web 检查器确认控制器元素的 data-slideshow-index-value
属性在你从一张幻灯片移动到下一张幻灯片时会发生变化。
﹟ 更改回调
我们修改后的控制器改进了原始版本,但对 this.showCurrentSlide()
的重复调用很突出。当控制器初始化以及每次更新 this.indexValue
的地方之后,我们必须手动更新文档的状态。
我们可以定义一个 Stimulus 值更改回调来清理重复并指定控制器在索引值更改时应如何响应。
首先,删除 initialize()
方法并定义一个新方法 indexValueChanged()
。然后从 next()
和 previous()
中删除对 this.showCurrentSlide()
的调用
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "slide" ]
static values = { index: Number }
next() {
this.indexValue++
}
previous() {
this.indexValue--
}
indexValueChanged() {
this.showCurrentSlide()
}
showCurrentSlide() {
this.slideTargets.forEach((element, index) => {
element.hidden = index !== this.indexValue
})
}
}
重新加载页面并确认幻灯片放映行为相同。
Stimulus 在初始化时和响应对 data-slideshow-index-value
属性的任何更改时调用 indexValueChanged()
方法。你甚至可以在 Web 检查器中摆弄该属性,控制器将相应地更改幻灯片。继续,试一试!
﹟ 设置默认值
还可以将默认值作为静态定义的一部分进行设置。这样做如下
static values = { index: { type: Number, default: 2 } }
如果控制器元素上未定义 data-slideshow-index-value
属性,这将从 2 开始索引。如果你有其他值,你可以混合匹配哪些需要默认值,哪些不需要
static values = { index: Number, effect: { type: String, default: "kenburns" } }
﹟ 总结和后续步骤
在本章中,我们了解了如何使用值来加载和持久化幻灯片控制器的当前索引。
从可用性的角度来看,我们的控制器是不完整的。当你查看第一张幻灯片时,上一个按钮似乎什么也不做。在内部,indexValue
从 0
减小到 -1
。我们能否让值环绕到最后一张幻灯片的索引?(下一个按钮也有类似的问题。)
接下来,我们将了解如何在 Stimulus 控制器中跟踪外部资源,例如计时器和 HTTP 请求。