Vue.js で watch と nextTick を使ってみる

データの変更を監視するウォッチャと DOM に反映され後に処理をする nextTick を使います。

ウィンドウのリサイズ処理について本家のフォーラムを調べていて、こちらの記事を見ていると $nextTick というのが使われていました。
たまに見かける nextTick ってどういう時に使うんだろう?とマニュアルを見てみると以下のような説明が書いてあります。

callback の実行を遅延し、DOM の更新サイクル後に実行します。DOM の更新を待ち受けるためにいくつかのデータを更新した直後に使用してください。

vm.$nextTick( [callback] )

ふむふむふむ。あーそういうことね完全に理解した。←分かってない。

DOM の更新後に処理をさせたい時に使うようです。具体的にどういう時に使うんでしょう~いまいち分からないので何か書いてみるのが一番はやいという事で Ajax の処理を書いてみました。

Ajax でデータを受け取ってコンテナに入れた時、高さを変化させるアニメーションを付けたいと思いますが、実際に DOM が更新されてみないとそのコンテナの高さはわかりませんよね。nextTick を使うと DOM が更新されたあとに確実に処理してくれます!

ボタンを押すとデータを受け取ってプロパティに入れます。0~1秒でランダムに擬似的な遅延をしています。watchmessage を監視して内容が変わったらボックスの高さを変更するようにしています。

<div id="app">
    <p><button v-on:click="getMessage" v-bind:disabled="isLoading">Get JSON</button> 各リストアイテムをクリックすると消えるよ!</p>
    <div id="box" v-bind:style="{height:boxHeight + 'px'}" v-bind:class="{loading:isLoading}">
        <div id="box-body">
            <p v-for="(item, idx) in messages" @click="remove(idx)">{{ item }}</p>
        </div>
        <transition name="fade">
            <div class="overlay" v-show="isLoading"><span>LOADING NOW</span></div>
        </transition>
    </div>
    <p>BOXの高さ:{{ boxHeight }} px</p>
</div>
if (!window.Promise) {
    window.Promise = ES6Promise && ES6Promise.Promise;
}
new Vue({
    el: '#app',
    data: function() {
        return {
            isLoading: false,
            messages: [],
            boxHeight: '',
        }
    },
    methods: {
        getMessage: function() {
            var self = this;
            self.isLoading = true;
            axios.post('1.php').then(function(response) {
                self.messages = response.data.message;
            }).catch().then(function(){
                self.isLoading = false;
            });
        },
        remove: function (idx) {
            this.messages.splice(idx, 1)
        }
    },
    watch: {
        messages: function() {
            this.$nextTick(function() {
                this.boxHeight = document.getElementById('box-body').getBoundingClientRect().height;
            });
        },
    },
    mounted: function() {
        this.messages = ['no message'];
    },
});

watchnextTick もすごい便利~!

本当は Vue の transition でアニメーションさせたかったのですが表示/非表示以外でのうまいアニメーション方法がわからなかったので CSS で普通に作成しました。

ただしアセットのロードは待たない

遅延するのは DOM の更新を待つ間だけなので、重い画像のロードで後から高さが変わったりする場合などは流石に対応できません。不特定なデータの高さを知るのは結構面倒くさいです。

気になる

でも、この nextTick ってどの DOM が更新されたかとか見てるわけじゃ無さそう?「~いくつかのデータを更新した直後に使用してください」という事はその時点で値が変わっているもの全部が DOM に反映された後に実行されるのでしょうか。具体的なパターンを思いつくわけじゃないけど丁度別の DOM が更新されたら…みたいな事はないのですかね。

ところで、最初に貼ったリンクで mounted 時のサイズ取得に nextTick を使っているのはなんででしょう。外しても同じ結果にみえるけど、ロード中によるサイドバーの有無とかのためでしょうか…?もう少し詳しく調べてみたいと思います。

まとめ

すごく便利で結構多用してます。

次はなにを勉強するか決まってませんが、SPA か状態管理についてそろそろやっていきたいなーと思います(•ө•)ノ