起きること
<template>
<div>
<!-- var1の値が変更されるたびリアクティブに表示も変更されるはず -->
<p>{{ var1 }}</p>
</div>
</template>
<script>
export default {
data() {
return {
var1: "oldvalue",
var2: "piyopiyo"
};
},
methods: {
hoge() {
// 1. 変数`var1`を変更する => この時点で表示は変わってほしい
this.var1 = "newvalue";
// 2. 時間のかかる処理`heavyProcess()`を待ち、その結果を使って後続の処理を行う(たとえばここではif())
if (await heavyProcess()) {
// 3. 後続処理
this.var2 = "piyo";
}
}
}
</script>
想定するのは...
awaitより前でvar1
を変更しているので、 2 や 3 を待つことなくビューは変更されnewvalue
と表示されている気がする。
実際には...
hoge()
が終わった時点で表示が変わる。
なぜ?
データ変更が検出されると、Vue はキューをオープンし、同じイベントループで起こる全てのデータ変更をバッファリングします。
methods に定義した関数や mounted()
など一つのイベントループとみなされる処理の中で複数の変数を変更するとき、それらの変更は個別にビューに反映されるのではなく、次の "tick" が来るときにまとめて反映が始まる。
つまり一つの処理内で変数var1を変更
=>何らかをawaitやthen()
=>変数var2を変更
とすると、var1
の変更がビューに反映されるのはvar2
の変更を待ってからになる。
どうすればいい?
データの変更後に Vue.js の DOM 更新の完了を待つには、データが変更された直後に Vue.nextTick(callback) を使用することができます。そのコールバックは DOM が更新された後に呼ばれます。
Vue.nextTick()
を利用して強制的にDOM更新を挟むことができる。
<script>
export default {
data() {
return {
var1: "oldvalue",
var2: "piyopiyo"
};
},
methods: {
hoge() {
// 1. 変数`var1`を変更する => この時点で表示も変わってほしい
this.var1 = "newvalue";
// α. DOM更新を起こす
this.$nextTick(() => {
// nextTick()のコールバックはDOM更新を待って実行が始まる
// 2. 時間のかかる処理`heavyProcess()`を待ち、その結果を使って後続の処理を行う(たとえばここではif())
if (await heavyProcess()) {
// 3. 後続処理
this.var2 = "piyo";
}
});
}
}
</script>
こうすれば、 2 や 3 はビューに 1 の結果が反映されてから実行される。
おまけ
なお、nextTick()
はPromiseを返すので、await や then() でより直感的に書くこともできる。
await this.$nextTick();
// DOM更新後に行いたい処理
this.$nextTick().then(() => {
// DOM更新後に行いたい処理
});