タイトル: $nextTickの使い方(ライフサイクルフック)
SEOタイトル: 【Vue.js】$nextTickの使い方(ライフサイクルフック)
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="1" cellspacing="1" style="width:1000px">
<thead><tr><th style="background-color:#f39c12;color:#ffffff;text-align:left">この記事の要点</th></tr></thead>
<tbody><tr><td style="background-color:#fef9e7"><ul>
<li><code>$nextTick</code> は<strong>DOM 反映後に処理を実行</strong>する Vue.js のメソッド</li>
<li>data を変更しただけだと DOM はまだ更新されていない</li>
<li><code>this.$nextTick(() => ...)</code> で次の DOM 反映タイミングを待つ</li>
<li>Vue 3 Composition API: <code>import { nextTick } from "vue"</code> + <code>await nextTick()</code></li>
<li>用途: DOM サイズ計算 / scroll 位置調整 / サードパーティライブラリ初期化</li>
</ul></td></tr></tbody>
</table>
<p> </p>
<h2>なぜ nextTick が必要か</h2>
<p>Vue は data 変更を検知すると<strong>次のティック</strong>で DOM を更新します(バッチ処理によるパフォーマンス最適化)。data を変更した直後に DOM を参照しても、まだ更新されていません。</p>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// 例: nextTick なしだと
data() {
return { message: "" };
},
methods: {
update() {
this.message = "Hello";
// この時点で this.$el.textContent は "" のまま!(DOM はまだ更新されていない)
const len = this.$el.querySelector("p").textContent.length;
console.log(len); // → 0
}
}</code></pre>
<h2>nextTick で DOM 反映を待つ</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// Vue 2 / Vue 3 Options API
methods: {
update() {
this.message = "Hello";
this.$nextTick(() => {
// DOM 反映完了後
const len = this.$el.querySelector("p").textContent.length;
console.log(len); // → 5
});
}
}
// async / await でも書ける
methods: {
async update() {
this.message = "Hello";
await this.$nextTick();
const len = this.$el.querySelector("p").textContent.length;
console.log(len);
}
}</code></pre>
<h2>Vue 3 Composition API</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code><script setup>
import { ref, nextTick } from "vue";
const message = ref("");
const el = ref(null);
async function update() {
message.value = "Hello";
await nextTick();
// DOM 反映完了
console.log(el.value.textContent); // → "Hello"
}
</script>
<template>
<p ref="el">{{ message }}</p>
<button @click="update">更新</button>
</template></code></pre>
<h2>典型的な使用シーン</h2>
<h3>① 動的に追加した要素のサイズ計算</h3>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>async addItem(item) {
this.items.push(item); // リストに追加
await this.$nextTick();
// 新しい要素の高さを計算
const newElem = this.$refs.list.lastElementChild;
const height = newElem.getBoundingClientRect().height;
console.log("追加した要素の高さ:", height);
}</code></pre>
<h3>② スクロール位置の調整</h3>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>async scrollToBottom() {
this.messages.push({ text: "新メッセージ" });
await this.$nextTick();
// メッセージが描画されてからスクロール
const container = this.$refs.messageContainer;
container.scrollTop = container.scrollHeight;
}</code></pre>
<h3>③ フォーカス制御</h3>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>async toggleEditMode() {
this.editing = true; // v-if で input 表示
await this.$nextTick();
// input が描画されてからフォーカス
this.$refs.editInput.focus();
}</code></pre>
<h3>④ サードパーティライブラリの再初期化</h3>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>async updateChart() {
this.chartData = newData;
await this.$nextTick();
// Chart.js を再描画
this.chart.update();
}
async refreshSlider() {
this.slides = newSlides;
await this.$nextTick();
// Swiper を再初期化
this.swiper.update();
}</code></pre>
<h2>watch との組み合わせ</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// Vue 3 Composition API
import { watch, nextTick, ref } from "vue";
const items = ref([]);
watch(items, async (newItems) => {
await nextTick();
// items が変更されて DOM が更新された後
console.log("DOM 更新完了");
});</code></pre>
<h2>nextTick が複数回ある場合</h2>
<p>同じティック内の複数の data 変更は<strong>1 回の DOM 更新</strong>にまとめられます:</p>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>methods: {
async updateMany() {
this.a = 1;
this.b = 2;
this.c = 3;
// すべての変更が 1 回の DOM 更新で反映される
await this.$nextTick();
// 全部反映済み
}
}</code></pre>
<h2>nextTick の代替</h2>
<p>シンプルなケースなら <code>setTimeout(fn, 0)</code> や <code>requestAnimationFrame</code> でも近い動作になりますが、<strong>Vue の更新サイクルと完全に同期する保証はない</strong>ため <code>nextTick</code> を使うのが安全:</p>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// ✗ 動くこともあるが、Vue の更新と同期しないことも
this.message = "Hello";
setTimeout(() => {
console.log(this.$el.textContent);
}, 0);
// ✓ 確実
this.message = "Hello";
this.$nextTick(() => {
console.log(this.$el.textContent);
});</code></pre>
<h2>注意点</h2>
<h3>無限ループに注意</h3>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// ✗ 危険
async update() {
this.count++;
await this.$nextTick();
this.count++; // また data 変更 → さらに DOM 更新サイクル
// → updated フックや watcher で意図せず連鎖発火することがある
}</code></pre>
<h3>テストでも nextTick</h3>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// Vue Test Utils
import { mount } from "@vue/test-utils";
import { nextTick } from "vue";
test("counter increments", async () => {
const wrapper = mount(Counter);
await wrapper.find("button").trigger("click");
await nextTick(); // DOM 反映を待つ
expect(wrapper.text()).toContain("Count: 1");
});</code></pre>
<h2>関連記事</h2>
<ul>
<li><a href="/article/21291">ライフサイクルフック(created / mounted / updated /...)</a></li>
</ul>