この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:T
更新日時:2026-06-11 07:07:02

タイトル: $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>&nbsp;</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>