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 操作を行ったり、テストコードで結果を検証しようとすると、期待した変更がまだ反映されていないことがあります。
この挙動は以下のような背景から生まれます。
-
リアクティブ更新のバッチ処理
Vue はデータ変更をまとめて次の tick で処理することで、過剰な再描画を防ぎます。$compileが内部でthis.$forceUpdateを呼び出すものの、実際の DOM 更新は次回のフラッシュフェーズで行われます。 -
$compileが返すコンポーネントインスタンスの非同期マウント
コンパイルされたテンプレートは即座に VNode に変換されますが、マウントされるタイミングは親コンポーネントのレンダリングが完了した後です。 -
Vue の次期バージョン(Vue 3)で
compileAPI が削除
Vue 3 ではテンプレート文字列コンパイルはビルド時に限定され、実行時コンパイルは公式にはサポートされません。そのため、将来的にthis.$compileに依存したコードは廃止が前提となります。
以上を踏まえると、this.$compile が即時に画面に反映されないのは「Vue のリアクティブ更新サイクルと非同期マウント」の特性によるものです。
実装とデバッグ手順
ここからは、実際に this.$compile が期待通りに動作しないケースを再現し、原因を特定し、確実に反映させるための実装パターンを示します。
ステップ1: 基本的な $compile の使い方
Jsexport 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 を忘れているケースです。
Jsmethods: { 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」になっている |
解決策
$nextTickでタイミングを合わせる
データ変更後に次の描画サイクルが走るまで待つことで、コンパイル時に最新のリアクティブデータが取得できます。
js
onClick() {
this.message = 'クリックで更新';
this.$nextTick(() => {
this.insertDynamic();
});
}
$forceUpdateを併用する
どうしてもタイミングが合わない場合は、手動で強制再描画させることも可能です。
js
onClick() {
this.message = 'クリックで更新';
this.$forceUpdate(); // データ変更を即座にフラッシュ
this.insertDynamic();
}
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);
}
}
- Vue 3 への移行を検討する
Vue 3 ではcompile系 API が公式に除外され、代わりに 動的コンポーネント (<component :is="...">) や Teleport、render 関数 が推奨されます。将来的な保守性を考えると、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 パターンを取り上げる予定です。
参考資料
- Vue.js 公式ドキュメント – リアクティビティシステム
- Vue.js 公式ガイド – コンパイルとランタイム
- vue-template-compiler GitHub リポジトリ
- Vue 3 ドキュメント – 動的コンポーネント
- 「Vue.js実践入門」(技術評論社, 2022) – 第4章「テンプレートエンジンの内部構造」