前回、前々回の記事ではVue.jsのスロットを使用して、親コンポーネントから子コンポーネントへコンテンツを埋め込めることを紹介しました。
コンポーネントを利用して画面を構築していると、データ(値)は子コンポーネントで取得しておいて、親コンポーネントではそのデータ含めたHTMLテンプレート(要素)を子コンポーネントのスロットに埋め込みたい時があります。
通常は、子コンポーネント内のデータ(dataオプションに定義したデータなど)は、子コンポーネント内からしかアクセスできませんが、子コンポーネント側のデータを親コンポーネントから参照したい場合が多々あります。
Vue.jsのスロットでは、そのような場合に子コンポーネントのデータを参照できる「スコープ付きスロット」という仕組みが用意されており、親コンポーネント側で子コンポーネントのデータにアクセスしてHTML要素(タグ)の内部に配置したコンテンツを子コンポーネントのスロットに埋め込むことができるようになっています。
今回は、スロットを利用して子コンポーネントのデータに親コンポーネントからアクセスする方法について紹介します。
スコープ付きスロット
スコープ付きスロットとは、子コンポーネントのスロットにデータ(値)をバインドし、バインドしたデータに親コンポーネントからアクセスすることができるようにする機能です。
スコープ付きスロットを利用することで、データを出力するロジックを子コンポーネント側に記述し、データを出力する際のHTMLの形式やスタイルの指定を親コンポーネント側で記述することができます。
子コンポーネント側の記述
子コンポーネント側では、slot要素でスロットを定義する際にスロットにバインドするデータをv-bindディレクティブで指定します。
1 |
<<slot v-bind:プロパティ名="バインドするデータ(値または式)"></slot> |
プロパティ名に指定する値は、親コンポーネントから参照する際に指定する名前になります。ここで指定するプロパティ(属性)のことを「スロットプロパティ」と言います。
バインドするデータには、子コンポーネント側のdataオプションに定義したプロパティなどを指定することができます。
親コンポーネント側の記述
親コンポーネント側では、HTMLテンプレートに子コンポーネントのカスタム要素を配置します。
子コンポーネントの要素の子要素としてtemplate要素を配置し、属性にv-slotディレクティブを定義します。
v-slotディレクティブには引数として子コンポーネントのスロットプロパティが格納されているオブジェクトを参照するための名前を指定します。
1 2 3 4 |
<コンポーネント v-bind:スロットの名前="スロットプロパティを含むオブジェクト"> {{スロットプロパティを含むオブジェクト.スロットプロパティ}} </コンポーネント> <> |
スロットの名前には、子コンポーネントのslot要素にname属性で設定した名前、または名前を設定していない場合は「default」を指定します。
スロットプロパティを含むオブジェクトには任意の値を指定できます。
親コンポーネントでは、ここで指定するスロットプロパティを含むオブジェクトのプロパティとして、子コンポーネントで設定したスロットプロパティを参照することができます。スロットプロパティは、スロットプロパティを含むオブジェクトの後にドットでつなげて記述します。
スコープ付きスロットの使用例
以下にスコープ付きスロットの使用例を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<!-- 子コンポーネント --> <script type="text/x-template" id="product-sheet"> <div> <slot v-bind:product="product" v-bind:maker="maker"> <div> {{product.name}}/{{product.price}}/{{maker}} </div> </slot> </div> </script> <!-- 親アプリケーション --> <div id="app"> <product-component> <template v-slot:default="slotProps"> <div> 製品名: {{slotProps.product.name}} </div> <div> 価格: {{slotProps.product.price}} </div> <div> 製造元: {{slotProps.maker}} </div> </template> </product-component> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 子コンポーネント const productComponent = { template: '#product-sheet', data: function() { return { product: { name: 'A製品', price: 1250 }, maker: '○×株式会社' } } } // 親アプリケーション Vue.createApp({ components: { 'product-component': productComponent } }).mount('#app') |
ここでは、子コンポーネントでdataオプションに定義したnameとpriceの2つのプロパティを持つオブジェクトのproductプロパティとmakerプロパティを、slot要素にv-bindディレクティブでバインドしています。
親コンポーネントでは、子コンポーネントでバインドしたスロットプロパティを参照しています。
スロットプロパティを受け取るためのオブジェクトをv-slotディレクティブに「slotProps」という名前で指定しています。
子コンポーネントでバインドしたスロットプロパティには「slotProps.プロパティ名」でアクセスし、マスタッシュ構文({{}})で値を出力しています。
上記の子コンポーネントのスロット既定のコンテンツは以下のように表示されます。
これをスロットを使用して親コンポーネントからコンテンツを出力すると、次のように差し替えることができます。
上記の例では、単純なデータの参照例を記載していますが、子コンポーネントでv-forディレクティブを使用して繰り返し出力したプロパティを親コンポーネントから参照することもできます。
以下に子コンポーネントでv-forディレクティブでループして出力したプロパティのデータを、親コンポーネントで取得して新たなコンテンツとして出力する例を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!-- 子コンポーネント --> <script type="text/x-template" id="product-list"> <div> <ul> <li v-for="(product, index) in products" v-bind:key="product.id"> <slot v-bind:product="product"></slot> </li> </ul> </div> </script> <!-- 親アプリケーション --> <div id="app"> <product-list> <template v-slot:default="slotProps"> <span> {{slotProps.product.name}} ({{slotProps.product.price}}円) </span> </template> </product-list> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// 製品リスト const products = [ { id: 1, name: '製品A', price: 1250 }, { id: 2, name: '製品B', price: 2500 }, { id: 3, name: '製品C', price: 1800 } ]; // 子コンポーネント const productList= { template: '#product-list', data: function() { return { products: products } } } // 親アプリケーション Vue.createApp({ components: { 'product-list': productList } }).mount('#app') |
上記の例では、製品リストを表示する子コンポーネント(procudt-list)のdataオプションのプロパティに、製品情報のオブジェクトを配列で持つproductsプロパティを定義してます。
定義したproductsプロパティはHTMLテンプレートのli要素でv-forディレクティブを使用してループで処理を行っています。
li要素内のテンプレートでは、slot要素でv-bindディレクティブを使用して、ループ処理をするために取得したproductsプロパティの1要素のproductをバインドしています。
【補足】デフォルトスロット(default)しかない場合の省略記法
本記事でここまで示した子コンポーネントにはデフォルトスロットが1つ配置されているだけでした。
このように、デフォルトスロットだけの場合は、親コンポーネントで定義するコンポーネント要素(タグ)をスロットのテンプレートとして使用することができます。
つまり、template要素を使用することなく、コンポーネントの要素(上記の例ではproduct-list)に対して直接v-slotディレクティブでの指定を使うことができます。
1 2 3 4 5 6 7 |
<div id="app"> <product-list v-slot:default="slotProps"> <span> {{slotProps.product.name}} ({{slotProps.product.price}}円) </span> </product-list> </div> |