Vue.jsでUIをモジュール化する際、コンポーネントをどのようにアプリケーションに組み込むかは、スコープの設計に直結します。登録方法は主に「グローバル登録」と「ローカル登録」に分類され、それぞれ適切なユースケースが存在します。また、ブラウザのDOM仕様やVueのリアクティブシステムの仕組みに起因する重要な制約事項も併せて理解する必要があります。
グローバル登録によるアプリケーション全体での共有
Vue.component() メソッドを利用すると、定義したコンポーネントはすべてのVueインスタンスのテンプレート内で直接使用可能になります。この方法で登録を行う場合、必ずルートインスタンスの生成(new Vue())よりも前に処理を実行してください。
<div id="app-root">
<notification-bar></notification-bar>
</div>
// グローバルコンポーネントの定義
Vue.component('notification-bar', {
template: '<div class="alert">システムメンテナンスのお知らせ</div>'
});
// ルートインスタンスのマウント
new Vue({
el: '#app-root'
});
上記コードは、ブラウザ上で以下の構造に展開されます。
<div id="app-root">
<div class="alert">システムメンテナンスのお知らせ</div>
</div>
ローカル登録によるスコープの限定
すべての部品をグローバル空間に公開すると、バンドルサイズの肥大化や命名衝突のリスクが生じます。特定の親コンポーネント内でのみ利用する場合は、オプションオブジェクトの components プロパティを通じてローカル登録を行うのが標準的なアプローチです。
const StatusIndicator = {
template: '<span class="status">処理完了</span>'
};
new Vue({
el: '#dashboard',
components: {
// 'status-indicator' タグはこのインスタンス内でのみ解決される
'status-indicator': StatusIndicator
}
});
実装時に留意すべき重要な制約
1. DOMテンプレートの解析制限と is 属性の活用
インラインテンプレート(el オプションで既存のDOMをマウント対象とする場合)を使用する際、VueはブラウザがHTMLをパースした後の結果を受け取ります。HTML仕様では、<table>、<ul>、<select> などの要素内に配置できる子要素が厳密に定められています。このルールに反するカスタムタグを直接配置すると、ブラウザによってDOMツリーから除外され、レンダリングが崩れる原因となります。
<!-- 誤: ブラウザが <custom-row> をテーブル外に追い出す可能性がある -->
<table>
<custom-row></custom-row>
</table>
この問題を回避するには、HTML標準要素に is 属性を付与し、Vueに対してコンポーネントとして振る舞うよう指示します。
<!-- 正: 標準の tr 要素としてパースさせ、Vue側でコンポーネントに置き換える -->
<table>
<tr is="custom-row"></tr>
</table>
2. data オプションは必ずファクトリ関数で定義する
コンポーネント定義において、data をプレーンなオブジェクトとして直接記述することは許可されていません。オブジェクトを直接渡した場合、そのコンポーネントのすべてのインスタンスが同じデータ参照を共有してしまい、1つのインスタンスでの状態変更が他すべてに波及する副作用が発生します。
// 誤: 参照が共有されるためVueが警告を出力する
const UserProfile = {
template: '<p>{{ username }}</p>',
data: {
username: 'default_user'
}
};
これを防ぐには、data を新しいオブジェクトを返す関数として定義します。これにより、インスタンスが生成されるたびに独立したリアクティブデータが確保されます。
// 正: 呼び出しごとに新規オブジェクトを返す
const UserProfile = {
template: '<p>{{ username }}</p>',
data: function() {
return {
username: 'default_user'
};
}
};