很多框架怂恿你在 JavaScript 中一直保存着状态。它们将 DOM 看作“只写”的渲染目标,由客户端模板根据来自服务端的 JSON 进行调解。

Stimulus 采用了不同的方式。Stimulus 应用程序的状态以 DOM 属性的方式存在;控制器本身很大程度上是无状态的。这种方式可以用于任意位置初始化的 HTML 文档,Ajax 请求、Turbolinks 访问,甚至另一个 JavaScript 库等,而且不需要任何显式步骤就能自动让控制器焕发活力。

构建幻灯片

在上一章,我们学习了 Stimulus 控制器可以通过添加 class 类名来维持简单的状态。但是在我们需要存储一个值也不是简单的标记时,应该怎么做呢?

我们将通过构建一个幻灯片控制器来研究这个问题,在幻灯片中,需要在属性里保存当前选中的滑块的索引。

同样,从 HTML 开始:

<div data-controller="slideshow">
  <button data-action="slideshow#previous">←</button>
  <button data-action="slideshow#next">→</button>

  <div data-target="slideshow.slide" class="slide">A</div>
  <div data-target="slideshow.slide" class="slide">B</div>
  <div data-target="slideshow.slide" class="slide">C</div>
  <div data-target="slideshow.slide" class="slide">D</div>
</div>

每个滑块目标代表幻灯片里的单个滑块。控制器将负责同一时间只有一个滑块被显示。

默认地,使用 CSS 隐藏所有滑块,仅在 slide--current class 被应用时才显示:

.slide {
  display: none;
}

.slide.slide--current {
  display: block;
}

现在,起草我们的控制器。新建文件 src/controllers/slideshow_controller.js

// src/controllers/slideshow_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "slide" ]

  initialize() {
    this.showSlide(0)
  }

  next() {
    this.showSlide(this.index + 1)
  }

  previous() {
    this.showSlide(this.index - 1)
  }

  showSlide(index) {
    this.index = index
    this.slideTargets.forEach((el, i) => {
      el.classList.toggle("slide--current", index == i)
    })
  }
}

控制器定义了一个方法 showSlide(),它将循环每个滑块目标,在滑块被匹配到时切换slide--current class。

通过显示第一个滑块来初始化控制器,next()previous() 操作方法用于向前或向后翻页。

生命周期回调函数解释

initialize() 方法有何作用?与之前所用的connect()方法有何不同?

有一些 Stimulus 回调方法,它们用于在控制器进入或离开文档时创建或销毁相关状态。

方法何时被 Stimulus 调用
initialize()仅一次,控制器被首次实例化时
connect()控制器连接到 DOM 时
disconnect()控制器与 DOM 断开连接时

刷新页面,确认 NEXT 按钮可以跳到下一个滑块。

从 DOM 读取初始状态

注意控制器如何追踪它的状态——当前被选中的滑块——在 this.index 属性 中。

现在,我们想要让第二个滑块替代第一个显示出来。我们应该如何对起始索引编码?

一种方式是从 HTML data 属性中加载初始索引。例如,可以为控制器元素添加 data-slideshow-index 属性:

<div data-controller="slideshow" data-slideshow-index="1">

然后,在 initialize()方法中,读取该属性,将它转换为整型,然后传递给 showSlide()

initialize() {
    const index = parseInt(this.element.getAttribute("data-slideshow-index"))
    this.showSlide(index)
  }

在控制器元素中使用 data 属性是很常见的方式,Stimulus 为此提供了 API。不同于直接读取属性的值,使用 this.data.get()更方便:

  initialize() {
    const index = parseInt(this.data.get("index"))
    this.showSlide(index)
  }

Data API 的解释 每个 Stimulus 控制器都有一个 this.data 对象和 has(), get(),和 set() 方法。这些方法为访问控制器元素的 dara 属性带来方便,有控制器标识符限定了作用域。

例如,在上面的控制器中:

  • this.data.has("index") 如果控制器元素拥有 data-slideshow-index 属性,则返回 true
  • this.data.get("index") 以字符串形式返回元素的 data-slideshow-index 属性的值
  • this.data.set("index", index) 将元素的 data-slideshow-index 属性的值设置为 index 的字符串值

如果你的属性名由一个以上单词注册,在 JavaScript 中以驼峰形式表示,在 HTML 中以属性形式表示。例如,读取data-slideshow-current-class-name属性可以使用this.data.get("currentClassName")

添加 data-slideshow-index属性到控制器元素,刷新页面,确认幻灯片从指定的滑块开始滚动。

在 DOM 中持久化状态

我们已经了解如何通过读取 data 属性来引导幻灯片的初始滑块索引。

在浏览幻灯片时,属性不会与控制器的 index 属性同步。如果在文档中科隆控制器元素,克隆的控制器会恢复到它的初始值。

可以通过为 index 属性定义代表 Data API 的 getter 和 setter 来改进控制器:

// src/controllers/slideshow_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "slide" ]

  initialize() {
    this.showCurrentSlide()
  }

  next() {
    this.index++
  }

  previous() {
    this.index--
  }

  showCurrentSlide() {
    this.slideTargets.forEach((el, i) => {
      el.classList.toggle("slide--current", this.index == i)
    })
  }

  get index() {
    return parseInt(this.data.get("index"))
  }

  set index(value) {
    this.data.set("index", value)
    this.showCurrentSlide()
  }
}

这里将 showSlide() 重命名为 showCurrentSlide()并修改使其从 this.index读取值。 get index() 以整型返回控制器元素的 data-slideshow-index 属性。set index()方法设置属性的值,然后刷新当前滑块。

支持,控制器的状态便完全保存在 DOM 中了。

总结和下一步

本章我们了解了如何使用 Stimulus 的 Data API 来加载和保存幻灯片控制器的当前索引。

从可用性角度,控制器尚未完成。考虑如何完善控制器,大概需要解决以下问题:

在查看第一个滑块时,上一页按钮什么事都不用干也显示出来了。在其内部,索引值从0降到-1。是否可以在这里点击上一页按钮跳到最后一个滑块的索引?(此问题同样存在于下一页按钮)。

如果忘了指定 data-slideshow-index 属性,get index() 方法中的 parseInt() 会返回 NaN。在此情况下,是否可以预设一个默认值 0?

下一步,我们将了解如何在 Stimulus 控制器中追踪外部资源,比如定时器和 HTTP 请求等。

Next: Working With External Resources

标签: none