Knockoutで作るカウントダウンタイマー(computedのすばらしさ)
Knockoutを使ってカウントダウンタイマーを作ります。
- ボタンを押すとカウトダウンを開始
- 1秒ごとに残り時間を減算
- もう一度ボタンを押すとカウントダウンを中止
スクリーンショット
設計
ドメインモデルを共有するアプリケーションが無いので、モデルには制約がありません。
モデルに制約がないため、必ずしもモデルとビューモデルを分ける必要はありません。
ここでは、勉強のためにMVVMに則り、モデルとビューモデルを分けます。
モデル
タイマーの
- 残り時間(ミリ秒)
- 減算処理
を持ちます。
ビューモデル
大きく三つの仕事があります。
表示用のプロパティ
- 残り時間のラベル
- 開始/中止ボタンのラベル(
開始
または中止
)
表示用の状態とその更新処理
- 残り時間
- タイマーの開始終了状態
- タイマーのカウント
イベントハンドラー
- カウントダウンのON/OFF切り替え
ビュー
- 残り時間(秒)
- 開始/中止ボタン
を表示します。
実装
モデル
残り時間と減算処理を実装します。
ふつうのJavaScriptのオブジェクトです。
残り時間はミリ秒単位で管理します。
初期値は10秒にしました。
model = {
restTime: 10000,
decrement(ms) {
this.restTime -= ms
}
}
ビューモデル
表示用のプロパティ
残り時間
observableを使って、変更を監視できるようにします。
this.restTime = ko.observable(model.restTime)
残り時間のラベル
モデルの持つ残り時間はミリ秒単位ですが、画面上は秒単位で表示します。
computedを使って、this.restTime
の値を秒単位に変換するobservable
を作ります。
this.restTimeLabel = ko.computed(() => this.restTime() / 1000)
ボタンのラベル
computedを使って、内部状態(bool値)を表示用に変換するobservable
を作ります。
this.toggleSwitchLabel = ko.computed(() => this._isStop() ? '開始' : '中止')
_isStop
は状態判定用の内部関数です。
_isStop() {
return this.state() === STATE_STOP
}
computed
は関数呼び出しの先のobservable
も検出できます。
タイマーの開始終了状態
this.toggleSwitchLabel
から参照するobservable
です。
this.state = ko.observable(STATE_STOP)
イベントハンドラー
カウントダウンのON/OFF切り替え
toggle() {
if (this._isStop()) {
this.state(STATE_RUN)
} else {
this.state(STATE_STOP)
}
}
this.state
もobservableです。この状態変更を監視して、カウント処理の開始・終了を制御します。
タイマーのカウント処理
subscribe
メソッドを使ってthis.state
の状態変更を監視します。
停止状態になったとき、window.clearIntervalでカウントダウンを止めます。
開始状態になったとき、window.setIntervalを使って1秒ごとに次の処理をします。
- モデルを減産
-
this.restTime
にモデルの値を反映
let intervalID = null
this.state.subscribe(() => {
if (this._isStop()) {
if (intervalID) {
window.clearInterval(intervalID)
intervalID = null
}
} else {
intervalID = window.setInterval(() => {
this.model.decrement()
this.restTime(this.model.restTime)
}, 1000)
}
})
このタイマーは自動では止まりません。
ビュー
残り時間と開始ボタンを表示します。
<span data-bind="text: restTimeLabel">100</span>秒
<button data-bind="text: toggleSwitchLabel, click: toggle">開始</button>
ソースコード全文
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-debug.js"></script>
<body>
<span data-bind="text: restTimeLabel">100</span>秒
<button data-bind="text: toggleSwitchLabel, click: toggle">開始</button>
<script>
const STATE_STOP = false,
STATE_RUN = true,
model = {
restTime: 10000,
decrement(ms) {
this.restTime -= ms
}
}
class TimerViewModel {
constructor(model) {
this.model = model
this.restTime = ko.observable(model.restTime)
this.state = ko.observable(STATE_STOP)
let intervalID = null
this.state.subscribe(() => {
if (this._isStop()) {
if (intervalID) {
window.clearInterval(intervalID)
intervalID = null
}
} else {
intervalID = window.setInterval(() => {
this.model.decrement(1000)
this.restTime(this.model.restTime)
}, 1000)
}
})
this.restTimeLabel = ko.computed(() => this.restTime() / 1000)
this.toggleSwitchLabel = ko.computed(() => this._isStop() ? '開始' : '中止')
}
toggle() {
if (this._isStop()) {
this.state(STATE_RUN)
} else {
this.state(STATE_STOP)
}
}
_isStop() {
return this.state() === STATE_STOP
}
}
//main
ko.applyBindings(new TimerViewModel(model))
</script>
</body>
感想
computedが気持ちよいです。
ReactivePropertyと使い心地が似ています。
関連記事
INotifyPropertyChanged実装のありえない面倒くささと、ReactivePropertyの信じられない素晴らしさ
Author And Source
この問題について(Knockoutで作るカウントダウンタイマー(computedのすばらしさ)), 我々は、より多くの情報をここで見つけました https://qiita.com/ledsun/items/bd4163c6cd1fecde4eb1著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .