言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

  1. トップページ
  2. Vue辞典
  3. スコープ付きスロット

スコープ付きスロット

対応: Vue 2(2016)

『Vue』のスコープ付きスロットは、子コンポーネントが持つデータをスロットのテンプレート内で利用できる仕組みです。通常のスロットでは親側のデータしか参照できませんが、スコープ付きスロットを使うと子コンポーネントが管理するデータを親側のテンプレートから参照して表示を自由にカスタマイズできます。

構文

<!-- 子コンポーネント側:<slot> に v-bind でデータを渡します -->

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <!-- :item="item" でスロットプロパティとして親にデータを公開します -->
      <slot :item="item">{{ item.name }}</slot>
    </li>
  </ul>
</template>
<!-- 親コンポーネント側:v-slot="変数名" でスロットプロパティを受け取ります -->

<template>
  <MyList>
    <!-- slotProps に子が渡した全プロパティがまとめて入ります -->
    <template v-slot:default="slotProps">
      <strong>{{ slotProps.item.name }}</strong>
    </template>
  </MyList>
</template>
<!-- 分割代入を使った省略記法 -->

<template>
  <MyList>
    <!-- { item } のように分割代入するとプロパティを直接参照できます -->
    <template #default="{ item }">
      <strong>{{ item.name }}</strong>
    </template>
  </MyList>
</template>
<!-- 名前付きスコープ付きスロット:name と v-bind を組み合わせます -->

<!-- 子コンポーネント側 -->
<template>
  <div>
    <!-- name="item" スロットに row データを渡します -->
    <slot name="item" :row="row" v-for="row in rows" :key="row.id"></slot>
  </div>
</template>
<!-- 親コンポーネント側:v-slot:スロット名="変数名" で受け取ります -->

<template>
  <MyTable>
    <!-- v-slot:item="{ row }" でスロット名とプロパティを同時に指定します -->
    <template v-slot:item="{ row }">
      <td>{{ row.name }}</td>
      <td>{{ row.price }}円</td>
    </template>
  </MyTable>
</template>

スコープ付きスロットの主な記法一覧

記法概要
v-slot:default="変数名"デフォルトスロットのスロットプロパティをオブジェクトとして受け取る記法です。変数名.プロパティ名 でデータを参照します。
v-slot:default="{ プロパティ名 }"分割代入を使ってスロットプロパティを直接取り出す記法です。テンプレート内で変数名なしにそのまま参照できます。
#default="{ プロパティ名 }"v-slot:default の省略記法です。#スロット名 と同じ意味になります。
v-slot:スロット名="変数名"名前付きスコープ付きスロットでスロットプロパティを受け取る記法です。名前と受け取り変数を同時に指定します。
:プロパティ名="値"(子側)子コンポーネントの <slot> タグに v-bind でデータを渡す記法です。複数のプロパティを渡すことができます。

サンプルコード

商品リストを管理する子コンポーネントが、各行の表示方法を親コンポーネントに委ねる例です。

<!-- ProductList.vue(子コンポーネント:商品データを管理し、表示を親に委ねます) -->

<template>
  <ul class="product-list">
    <li v-for="product in products" :key="product.id">
      <!-- product オブジェクト全体をスロットプロパティとして親に渡します -->
      <slot :product="product">
        <!-- 親から内容が渡されなかった場合はこのデフォルト表示になります -->
        {{ product.name }}
      </slot>
    </li>
  </ul>
</template>

<script>
export default {
  name: 'ProductList',
  data() {
    return {
      // 表示する商品の一覧データです
      products: [
        { id: 1, name: 'ノートパソコン', price: 89800, stock: 5 },
        { id: 2, name: 'マウス',         price: 2980,  stock: 20 },
        { id: 3, name: 'キーボード',     price: 6800,  stock: 0 },
      ],
    };
  },
};
</script>
<!-- App.vue(親コンポーネント:スロットプロパティを受け取って独自スタイルで表示します) -->

<template>
  <div>
    <ProductList>
      <!-- #default="{ product }" で分割代入しながらスロットプロパティを受け取ります -->
      <template #default="{ product }">

        <!-- 在庫があるかどうかで表示スタイルを切り替えます -->
        <span :class="{ 'out-of-stock': product.stock === 0 }">
          {{ product.name }}
        </span>

        <!-- 価格を右寄せで表示します -->
        <span class="price">{{ product.price.toLocaleString() }}円</span>

        <!-- 在庫が0の場合は「在庫なし」バッジを表示します -->
        <span v-if="product.stock === 0" class="badge">在庫なし</span>

      </template>
    </ProductList>
  </div>
</template>

<script>
// ProductList を import してコンポーネントとして登録します
import ProductList from './ProductList.vue';

export default {
  name: 'App',
  components: { ProductList },
};
</script>

<style scoped>
/* 在庫なし商品はグレーアウトします */
.out-of-stock { color: #aaa; text-decoration: line-through; }

/* 価格は右側に余白を空けて配置します */
.price { margin-left: 8px; font-weight: bold; }

/* 在庫なしバッジの見た目を定義します */
.badge { margin-left: 6px; padding: 2px 6px; background: #e55; color: #fff; border-radius: 4px; font-size: 0.75em; }
</style>

次の例では、複数のスロットプロパティを渡してより細かい制御を行う方法を示します。

<!-- DataTable.vue(子コンポーネント:行データと行インデックスを両方渡します) -->

<template>
  <table>
    <tbody>
      <tr v-for="(row, index) in rows" :key="row.id">
        <!-- row(行データ)と index(行番号)をどちらも親に公開します -->
        <slot name="row" :row="row" :index="index"></slot>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  name: 'DataTable',
  props: {
    // 外部から行データの配列を受け取ります
    rows: {
      type: Array,
      required: true,
    },
  },
};
</script>
<!-- App.vue(親コンポーネント:row と index を分割代入して受け取ります) -->

<template>
  <DataTable :rows="members">
    <!-- v-slot:row="{ row, index }" で名前付きスコープ付きスロットを受け取ります -->
    <template v-slot:row="{ row, index }">
      <!-- index を使って1始まりの行番号を表示します -->
      <td>{{ index + 1 }}</td>
      <td>{{ row.name }}</td>
      <td>{{ row.role }}</td>
    </template>
  </DataTable>
</template>

<script>
import DataTable from './DataTable.vue';

export default {
  name: 'App',
  components: { DataTable },
  data() {
    return {
      // テーブルに表示するメンバーの一覧です
      members: [
        { id: 1, name: '田中 太郎', role: '管理者' },
        { id: 2, name: '鈴木 花子', role: '編集者' },
        { id: 3, name: '佐藤 次郎', role: '閲覧者' },
      ],
    };
  },
};
</script>

概要

スコープ付きスロットは『Vue』において、子コンポーネントが持つデータを親側のスロットテンプレートから参照できるようにする仕組みです。子コンポーネント側では <slot :プロパティ名="値"> のように v-bind でデータを公開し、親コンポーネント側では v-slot="変数名" または分割代入の v-slot="{ プロパティ名 }" で受け取ります。

この仕組みが特に有効なのは、リストや表のような繰り返し構造を持つコンポーネントです。データの管理・繰り返し処理は子コンポーネントに任せつつ、各行や各アイテムをどのようなマークアップで表示するかという決定権を親コンポーネントに委ねることができます。これにより、同じ子コンポーネントを使いながら表示形式だけを複数のページで使い分けることが可能になります。

省略記法として #スロット名="変数名" が使えます。デフォルトスロットの場合は #default="{ プロパティ名 }" のように書きます。スロットの基本については slot も、コンポーネントの基礎については component も、親子間のデータ受け渡しについては props もあわせてご覧ください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。