🔫

VueのnextTick()とは何か

2024/02/03に公開

Vue.jsのnextTick()とは何かを解説する記事です。
公式でドキュメントでは以下のように記載されています。
https://ja.vuejs.org/api/general

ドキュメントでの解説

次の DOM 更新処理を待つためのユーティリティーです。
Vue でリアクティブな状態を変更したとき、その結果の DOM 更新は同期的に適用されません。その代わり、Vue は「次の tick」まで更新をバッファリングし、どれだけ状態を変更しても各コンポーネントの更新が一度だけであることを保証します。
状態を変更した直後に nextTick() を使用すると、DOM 更新が完了するのを待つことができます。引数としてコールバックを渡すか、戻り値の Promise を使用できます。

<script setup>
import { ref, nextTick } from 'vue'

const count = ref(0)

async function increment() {
  count.value++

  // DOM はまだ更新されていない
  console.log(document.getElementById('counter').textContent) // 0

  await nextTick()
  // ここでは DOM が更新されている
  console.log(document.getElementById('counter').textContent) // 1
}
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>

ドキュメントの日本語難しいけど、サンプルコードを読むと要するにDOMの更新を待ってから次の処理に移ること?
もう少し掘り下げて理解してみることにします。

nextTick()の動作の原理

Vue.jsのnextTickの概念をより理解しやすく説明します。ここでのポイントは、Vue.jsがデータの変更をどのようにDOMに反映させるか、そしてnextTickがそのプロセスにどう関わるかに焦点を当てます。

VueのデータとDOM更新のプロセスは以下のようになっています。

  1. データ変更の検出: Vue.jsは、アプリケーションのデータに対する変更をリアクティブに検出
  2. 非同期更新キュー: データが変更されると、Vue.jsはその変更を非同期キューに追加。 このプロセスにより、短い時間内に発生した複数のデータ変更がまとめて処理され、効率的なDOM更新が可能になります。
  3. 一括でのDOM更新: 同一イベントループ内で集められたデータ変更は、一括でDOMに適用されます。これにより、複数の変更があってもDOMの更新は一度だけ行われ、パフォーマンスが向上します。

nextTickは、この更新キューが処理された後、即座に実行されるコールバックを登録する機能を提供します。具体的には、Vueの更新サイクルが完了し、DOMが最新の状態に更新された直後に、nextTickに渡された関数が実行されます。
nextTickを使用することで、データ変更後に更新されたDOMに対して確実にアクセスし、操作を行うことができます。これはデータ更新後の動的なUI更新や、特定の要素に対する操作(フォーカスの設定やスクロール位置の調整など)を必要とする場合に特に有用になるものだと思います。

nextTick使用例

実際に業務で使用したことや、それ以外にもどのようなユースケースがあるのかを考えてみました。

例1

Vueのデータが更新された後、その変更がDOMに適用されるまでには非同期の遅延があります。もし、この更新されたDOM要素に対して何か操作をしたい場合(例えば、フォーカスを設定する、高さを計算するなど)、nextTickを使用してその操作を実行することができます。

シナリオ:
ユーザーがフォームに入力し、その入力に基づいて表示されるメッセージを更新する場合を考えます。メッセージが更新された後、そのメッセージの高さを計算して、何らかのレイアウト調整を行いたいとします。

export default {
  data() {
    return {
      message: '初期メッセージ'
    };
  },
  methods: {
    updateMessageAndCalculateHeight(newMessage) {
      this.message = newMessage;
      this.$nextTick(() => {
        // ここでDOMは更新されており、新しいメッセージの高さを計算できる
        const messageHeight = this.$refs.messageContainer.offsetHeight;
        console.log(`メッセージの高さ: ${messageHeight}`);
      });
    }
  }
}

例2

コンポーネントがマウントされた後、すぐに子コンポーネントやDOM要素に対して操作を行いたい場合があります。この操作には、サイズの計測や外部ライブラリの初期化などが含まれます。mountedフック内でnextTickを使用すると、すべての子要素が正しくマウントされ、DOMが完全に描画された状態でコードを実行できます。

シナリオ:
ページのロード直後に特定の入力フィールドにフォーカスを自動的に設定したい場合を考えます。このフィールドは、コンポーネントが完全にマウントされた後でないとアクセスできません。

mounted() {
  this.$nextTick(() => {
    this.$refs.inputField.focus();
  });
}

まとめ

DOMを検知して再計算するcomputedを用いてもやりたいことを満たせるケースは多いように思いました。
computedとnextTickの違いに基づく実装の選択としては以下でしょうか

  • UIの更新をデータの変更に基づいて自動化したい場合:データの表示形式を変更する、または複数のデータソースを基にした集計結果を表示するような場合には、computedプロパティ
  • DOMの更新が完了した後に何か操作をしたい場合:例えば、Vueのリアクティブな更新によってDOM要素が追加された後に、その要素にフォーカスを設定する、またはサードパーティのライブラリを初期化する場合には、nextTick

一部のケースではcomputedプロパティだけでUIの更新が十分に処理できるかもしれませんが、computedプロパティは主にデータのリアクティブな変換に焦点を当てており、DOMの更新サイクルを直接的に制御するためのものではありません。一方で、nextTickはDOMの更新サイクルと関連するタイミングに敏感な操作に適しいるものと思います。

Discussion