markdown

はじめに (対象読者・この記事でわかること)

この記事は、Vue.js 2 系でコンポーネントの動的テンプレート生成を行う開発者、特に this.$compile を利用した経験がある方を対象としています。this.$compile を呼び出しても画面に変更がすぐに反映されず、デバッグに時間が取られるケースに直面したことがある方は必見です。本記事を読むことで、即時反映されない原因を体系的に把握し、回避策や代替実装をコードサンプルと共に習得できます。また、Vue の内部レンダリングフローや次期バージョンへの移行指針も併せて理解できるようになります。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • Vue.js 2.x の基本的なコンポーネント構造とライフサイクルフック
  • JavaScript(ES6)および HTML テンプレート文字列の扱い
  • npm / Vue CLI でのプロジェクト構築経験

概要と背景

Vue.js では、コンポーネントのテンプレートはコンパイル時に解析され、仮想DOM(VNode)へ変換されます。this.$compile は実行時に文字列テンプレートをコンパイルし、現在のコンテキストにバインドして DOM に注入できる便利な API ですが、Vue 2 系の内部実装では非同期的に次の更新サイクル(nextTick)で描画が行われます。そのため、this.$compile 後すぐに DOM 操作を行ったり、テストコードで結果を検証しようとすると、期待した変更がまだ反映されていないことがあります。

この挙動は以下のような背景から生まれます。

  1. リアクティブ更新のバッチ処理
    Vue はデータ変更をまとめて次の tick で処理することで、過剰な再描画を防ぎます。$compile が内部で this.$forceUpdate を呼び出すものの、実際の DOM 更新は次回のフラッシュフェーズで行われます。

  2. $compile が返すコンポーネントインスタンスの非同期マウント
    コンパイルされたテンプレートは即座に VNode に変換されますが、マウントされるタイミングは親コンポーネントのレンダリングが完了した後です。

  3. Vue の次期バージョン(Vue 3)で compile API が削除
    Vue 3 ではテンプレート文字列コンパイルはビルド時に限定され、実行時コンパイルは公式にはサポートされません。そのため、将来的に this.$compile に依存したコードは廃止が前提となります。

以上を踏まえると、this.$compile が即時に画面に反映されないのは「Vue のリアクティブ更新サイクルと非同期マウント」の特性によるものです。

実装とデバッグ手順

ここからは、実際に this.$compile が期待通りに動作しないケースを再現し、原因を特定し、確実に反映させるための実装パターンを示します。

ステップ1: 基本的な $compile の使い方

Js
export default { data() { return { rawTemplate: '<p>動的に生成されたテキスト: {{ message }}</p>', message: 'Hello Vue!' }; }, methods: { insertDynamic() { // 文字列テンプレートをコンパイル const Comp = this.$compile(this.rawTemplate); // コンパイル結果を現在のコンテキストにバインドしてマウント const vm = new Comp({ parent: this, data: { message: this.message } }).$mount(); // 生成された要素を任意の場所に挿入 this.$refs.container.appendChild(vm.$el); } } };

このコードは insertDynamic が呼び出された瞬間にテンプレート文字列をコンパイルし、 container という参照要素に挿入します。ポイントnew Comp(...).$mount() で手動マウントしている点です。

ステップ2: 即時反映しないケースの再現

実務でよくあるシナリオは、ユーザー操作直後に結果を UI に反映したいが、this.$nextTick を忘れているケースです。

Js
methods: { onClick() { this.message = 'クリックで更新'; this.insertDynamic(); // → ここで DOM がまだ更新されていない console.log(this.$refs.container.innerHTML); // 空文字列になる } }

上記の例では、message を変更した直後に insertDynamic を呼び出すため、コンパイル時点の message が古い値(初期値)になるか、まったく反映されません。結果として innerHTML が期待した内容と一致しません。

ハマった点やエラー解決

発生した現象 原因 発見した手掛かり
テンプレートの変数が更新されない this.message が更新された直後に $compile が走っている コンソールで this.message の値は変わっているが、生成された HTML が古い
appendChild 後に要素が消える コンパイルされたコンポーネントが親の再描画で上書きされる Vue のデバッグツールで子コンポーネントが「inactive」になっている

解決策

  1. $nextTick でタイミングを合わせる
    データ変更後に次の描画サイクルが走るまで待つことで、コンパイル時に最新のリアクティブデータが取得できます。

js onClick() { this.message = 'クリックで更新'; this.$nextTick(() => { this.insertDynamic(); }); }

  1. $forceUpdate を併用する
    どうしてもタイミングが合わない場合は、手動で強制再描画させることも可能です。

js onClick() { this.message = 'クリックで更新'; this.$forceUpdate(); // データ変更を即座にフラッシュ this.insertDynamic(); }

  1. Vue.compile(グローバル)を使う
    this.$compile が非推奨になる前提で、ビルド時にコンパイル済み関数を取得しておく方法です。vue-template-compiler で事前にテンプレートを関数化し、ランタイムでは単に関数を呼び出すだけにします。

js // compile.js (ビルド時に実行) const Vue = require('vue'); const compiler = require('vue-template-compiler'); const compiled = compiler.compileToFunctions('<p>{{ message }}</p>'); module.exports = compiled.render;

js // コンポーネント側 import renderFn from '@/compile.js'; methods: { insertDynamic() { const Comp = Vue.extend({ render: renderFn, data: () => ({ message: this.message }) }); const vm = new Comp({ parent: this }).$mount(); this.$refs.container.appendChild(vm.$el); } }

  1. Vue 3 への移行を検討する
    Vue 3 では compile 系 API が公式に除外され、代わりに 動的コンポーネント (<component :is="...">) や Teleportrender 関数 が推奨されます。将来的な保守性を考えると、this.$compile に依存しない実装に置き換えるのがベストです。

```vue

```

まとめ

本記事では、Vue.js の this.$compile が即時に画面に反映されない原因 として、リアクティブ更新のバッチ処理・非同期マウントという内部メカニズムを解説しました。その上で、$nextTick$forceUpdate、事前コンパイル、そして Vue 3 への移行 といった実践的な対策を提示し、コード例を交えて具体的な実装手順を示しました。

  • 原因:Vue の更新サイクルが非同期で、this.$compile の結果が次の tick で描画される
  • 即時反映のコツ:データ変更後に $nextTick で遅延させる、または $forceUpdate で強制描画
  • 将来の方針this.$compile に依存しない動的コンポーネントや render 関数へのリプレイス

この知識を活かせば、開発中に「テンプレートが反映されない」デバッグ時間を大幅に削減でき、メンテナンス性の高いコードベースへと移行できます。次回は、Vue 3 の defineAsyncComponent と Teleport を組み合わせた高度な動的 UI パターンを取り上げる予定です。

参考資料