跳至内容

管理状态

大多数当代框架鼓励你始终在 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)
}

// …
}

重新加载页面并验证控制台显示 1Number

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" } }

总结和后续步骤

在本章中,我们了解了如何使用值来加载和持久化幻灯片控制器的当前索引。

从可用性的角度来看,我们的控制器是不完整的。当你查看第一张幻灯片时,上一个按钮似乎什么也不做。在内部,indexValue0 减小到 -1。我们能否让值环绕到最后一张幻灯片的索引?(下一个按钮也有类似的问题。)

接下来,我们将了解如何在 Stimulus 控制器中跟踪外部资源,例如计时器和 HTTP 请求。

下一步:使用外部资源