VueのSlot

2019/08/04

v-slot

slotの基本的な使い方

v-slotを使うとコンポーネントの一部を親コンポーネントから変更することができます。

MyButtonコンポーネントを以下のように作成します。
<button>の中のテキスト部分を<slot>で囲んでいます。

//MyButton.vue
<template>
  <button><slot>送信</slot></button>
</template>

まずは特にslotを使わずボタンを置いてみます。
すると、MyButtonでで指定しているデフォルトのテキストが描画されます。

<template>
  <div>
    <MyButton></MyButton>
    //自己終了型で<MyButton />推奨
  </div>
</template>

結果

<div>
  <button>送信</button>
</div>

次はMyButtonの中に表示したいテキストを入れてみます。
すると、ボタンのテキスト部分(子コンポーネントで<slot>で囲んだ部分)に入れたテキストが描画されます。

<template>
  <div>
    <MyButton>戻る</MyButton>
  </div>
</template>

結果

<div>
  <button>戻る</button>
</div>

このように<slot>を使用することで子コンポーネントの一部を親コンポーネントから指定して変更することができます。

名前つきスロット

先ほどの例ではslotは1つですが、複数のslotを使用するために<slot>にはname属性で名前をつけることができます。
(name属性を指定していないslotは暗黙的に「default」という名前を持ちます。)

構文

<slot name="slotの名前"></slot>

「セクションのタイトル」、「説明文」をそれぞれslotで囲みname属性を指定してみます。

//SectionContent.vue
<template>
  <div class="section-content">
    <h2><slot name="title"></slot></h2>
    <p><slot name="summary"></slot></p>
  </div>
</template>

名前つきスロットにコンテンツを指定するには、<template>にv-slot:スロット名 で指定します。
v-slotで指定したところがそれぞれ親コンポーネントで指定したものが描画されます。

<template>
  <div>
    <section>
      <SectionContent>
        <template v-slot:title>セクションタイトル1</template>
        <template v-slot:summary>セクションテキスト1セクションテキスト1セクションテキスト1</template>
      </SectionContent>
    </section>
    <section>
      <SectionContent>
        <template v-slot:title>セクションタイトル2</template>
        <template v-slot:summary>セクションテキスト2セクションテキスト2セクションテキスト2</template>
      </SectionContent>
    </section>
  </div>
</template>

結果

<div>
  <section>
    <div class="section-content">
      <h2>セクションタイトル1</h2>
      <p>セクションテキスト1セクションテキスト1セクションテキスト1</p>
    </div>
  </section>
  <section>
    <div class="section-content">
      <h2>セクションタイトル2</h2>
      <p>セクションテキスト2セクションテキスト2セクションテキスト2</p>
    </div>
  </section>
</div>

※slotが1つのみ(名前なしのdefaultのslotのみ)の場合を除き v-slotは<template>にのみ指定できます。

NG
複数の場合<template>以外にはv-slotを指定することはできないのでエラー

  <section>
    <SectionContent>
      <div v-slot:title>セクションタイトル1</template>
      <div v-slot:summary>セクションテキスト1セクションテキスト1セクションテキスト1</div>
    </SectionContent>
  </section>

スコープ付きスロット

子コンポーネントで、dataのuser.lastNameが描画されるようにslotを作成します。

//CurrentUser.vue
<template>
  <div>
    <slot>{{user.lastName}}</slot>
  </div>
</template>

<script>
  export default {
    name: "CurrentUser",
    data(){
     return {
       user: {
         firstName: 'Taro',
         lastName: 'Yamada'
       }
     }
    }
  }
</script>

親コンポーネントで使用すると指定した通りにuser.lastNameが描画されます。

<template>
    <CurrentUser></CurrentUser>
    //自己終了形式<CurrentUser />推奨
</template>

結果

  <div>Yamada</div>

これをfirstNameの代わりにlastNameが表示したいときがあるかもしれません。
このとき親コンポーネント側で以下のように指定しても想定通り動作しません。
これはuserにアクセスすることができるのは子コンポーネントのCurrentUserコンポーネントのみで親コンポーネントからはアクセスすることができないからです。

NG

<template>
  <CurrentUser>
    {{user.lastName}}
  </CurrentUser>
</template>

親コンポーネントでスロットコンテンツとして子コンポーネントのuserを使えるようにするには、<slot>要素の属性としてuserをbindします。

//CurrentUser.vue
<template>
  <slot v-bind:user="user">{{user.lastName}}</slot>
</template>

bindされた要素はスロットプロパティと呼ばれ、 親スコープ内でv-slotの値として名前を指定することで、スロットプロパティとして受け取ることができます。
以下の例では名前をslotPropsとして受け取っていますが、ここは自分で名前をつけられます。

slot.user.firstNameとすることで想定通りの動作になります。

<CurrentUser>
  <template v-slot:default="slotProps">
    {{slotProps.user.firstName}}
  </template>
</CurrentUser>

結果

<div>Taro</div>

デフォルトスロットだけの場合のみtemplateタグを使用せず、コンポーネントタグに直接 v-slot を指定することができます。

<CurrentUser v-slot:default="slotProps">
  {{slotProps.user.firstName}}
</CurrentUser>

また、<slot>にname属性を指定しないとdefaultとなるのと同様に
引数のないv-slotもdefaultを参照するため、省略するできます。

<CurrentUser v-slot="slotProps">
  {{slotProps.user.firstName}}
</CurrentUser>

複数のスロットがある場合は必ず全てのスロットに対して<template>を使用する!

スロットプロパティを取得するとき分割代入を使用することができ、この場合より短く書くことができます。

<CurrentUser v-slot="{ user }">
  {{ user.firstName }}
</CurrentUser>

分割代入でスロットプロパティ取得する場合プロパティをリネームすることもできます。
user→person

<CurrentUser v-slot="{ user:person }">
  {{ person.firstName }}
</CurrentUser>

Vue公式ドキュメント参考