前一篇文章Stimulus:浏览器不支持复制或者弱网条件下,怎么办?,我们接着看Stimulus的状态管理。
大多数框架都鼓励我们您始终将状态保存在JavaScript中。他们把DOM作为一个只写的渲染目标。
Stimulus在这点上有所不同。一个Stimulus应用的各种状态都保存在DOM的属性上;controllers本身是无状态的。这一点使得Stimulus在任何地方都可以和HTML融洽的工作,包括初始化document,一个Ajax请求,一个Turbo visit,或者另一个js库,并且关联的controllers是自动启动的,而不需要任何显式的初始化操作。
上一篇文章中,我们学习了Stimulus的controller如何通过给一个元素添加一个class来维护简单的状态。但是,如果我们要存储一个值的话,而不是简单的标志,我们应该怎么做呢?
我们来看看这个问题,创建一个名为slideshow的controller,它会记住当前展示的第几个幻灯片。
我们写一段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>
写了4个幻灯片,属性设置为data-slideshow-target="slide",我们的controller负责在同一时间只显示一个幻灯片。
接下来草写一下controller的代码。创建一个新文件,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
})
}
}
在controller中的showCurrentSlide方法中,我们通过this.slideTargets遍历了所有的幻灯片,如果它的下标和当前记录的下标相同,那么就设置元素的hidden属性为false,显示出来,否则就隐藏起来。
我们初始化controller时,默认显示第一个幻灯片,然后next()和previous()方法控制显示下一个还是上一个。
这里我们提及了initialize()方法,initialize()方法干了什么?它和我们之前使用的connect()方法有什么不同?他们都是Stimulus的回调方法。
下面这些都是Stimulus生命周期中的回调方法,他们在配置或者结束连接状态时很有用。
initialize() 只执行一次,当controller第一次被实例化的时候
connect() 只要controller连接到DOM元素就会执行
disconnect() 只要controller断开与DOM元素的连接时就会执行
下面我们尝试从DOM元素读取幻灯片的初始状态。
注意下,目前,我们的controller是如何跟踪它的状态(当前选中的幻灯片),就是通过this.index属性。
但是如果我不想一开始就展示第一个幻灯片,而是展示第二个,那怎么办呢?
有一个办法就是通过读取HTML的data属性,获取初始的幻灯片索引。例如,可以添加data-index属性到controller连接到元素内:
<div data-controller="slideshow" data-index="1">
然后在initialize()方法中,我们读一下data-index属性值
initialize() {
this.index = Number(this.element.dataset.index)
this.showCurrentSlide()
}
这看起来完成了,但是方法很笨重,需要我们自己定义属性名,并且如果我们想再次访问这个index值,或者增加index值,需要持久化到DOM中时,完全没有帮助。
Stimulus的controller支持使用value属性,而且这些value属性会自动映射为data属性。只需要在controller类中定义一下:
static values = { index: Number }
Stimulus会创建controller属性this.indexValue,它被连接到元素的data-slideshow-index-value属性,并且自动做了数值转换。
在HTML中添加对应的关联属性:
<div data-controller="slideshow" data-slideshow-index-value="1">
再修改一下controller,添加static values的定义到controller中,然后改一下initialize()方法,日志打印一下this.indexValue:
export default class extends Controller {
static values = { index: Number }
initialize() {
console.log(this.indexValue)
console.log(typeof this.indexValue)
}
// …
}
刷新页面,看下console中是否打印了1和Number
和targets类似,我们在controller类中定义了values,并且描述为static静态的对象。在这个案例中,我们定义了一个类型为数值的index对象属性。
现在我们来更新controller中的initialize()方法和其他的方法,把this.index替换成this.indexValue。这样看起来才算真正完成了任务。
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
})
}
}
刷新页面,用浏览器的元素检查功能看一下controller连接的元素,看看当幻灯片切换后,它的data-slideshow-index-value属性值是否变化。
和最初版本比较的话,我们的controller已经有了很大提升,但是仔细你会发现showCurrentSlide()方法被重复调用了很多次。当controller初始化时,还有更新this.indexValue时,我们要手动更新document的状态。接下来我们重构一下代码,消除这部分的重复。
我们可以定义一个值变化的回调,告诉controller当index的值变化时应该怎么做。
首先,删除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
})
}
}
刷新页面,确认一下幻灯片行为还是和之前一样。
在controller初始化时,还有改变data-slideshow-index-value属性值时,Stimulus都会调用indexValueChanged()方法。
另外,属性值是可以设置默认值的,比如:
static values = { index: { type: Number, default: 2 } }
如果controller连接的元素没有设置data-slideshow-index-value属性时,幻灯片将从下标为2的幻灯片开始。如果还有其他的值,可以混在一起写:
static values = { index: Number, effect: { type: String, default: "kenburns" } }
本文,我们已经了解到如何使用values获取并持久化幻灯片的当前下标。
从可用性的角度来看,我们的controller是不完整的。当你看到第一个幻灯片的时候,再按previous按钮,就出问题了,indexValue从0减到-1。我们可以把indexValue设置为最后一个幻灯片的下标。那个Next按钮也有同样的问题。
下一篇文章,我们将看看如何在Stimulus的controller中跟踪外部资源,例如计时器和HTTP请求。