Vueのslotについて

直近でやっているタスクでVueのslotの使い方で少しハマってしまったところがあったので自分用にまとめてみます。

以下全てVue2系の話です。

slotの基本

コンポーネントに親コンポーネントから要素を渡すことができます。

たとえば、

<template>
  <div>
    <slot />
  </div>
</template>

<script>
export default {
  name: 'hoge'
}
</script>

このようなhogeコンポーネントがあったとすると、

<template>
  <hoge>
    fugafuga
  </hoge>
</template>

<script>
import hoge from './hoge'
export default {
  name: 'fuga',
  components: {
    hoge
  }
}
</script>

このような形で渡すことができます。<slot />の部分にはfugafugaという文字列が入ります。

<template>
  <hoge>
    <piyo />
  </hoge>
</template>

<script>
import hoge from './hoge'
import piyo from './piyo'
export default {
  name: 'fuga',
  components: {
    hoge,
    piyo
  }
}
</script>

こんな感じでコンポーネントを渡すこともできます。

名前付きslot

複数のslotを使いたい場合、各スロットに名前をつけることができます。

<template>
  <div>
    <slot name="title" />
    <slot name="body" />
  </div>
</template>

<script>
export default {
  name: 'hoge'
}
</script>

このようにすると、

<template>
  <hoge>
    <template #title>
      title
    </template>
    <template #body>
      body
    </template>
  </hoge>
</template>

<script>
import hoge from './hoge'
export default {
  name: 'fuga',
  components: {
    hoge
  }
}
</script>

こうするとtitleとbodyがそれぞれ指定したslotに入ります。

#titleは省略記法で省略せずに書くとv-slot:titleになります。

また、nameの無いslotには暗黙的にdefaultという名前が付きます。

スコープ付きslot

コンポーネントからslotのプロパティにアクセスできるようにするには以下のようにbindします。

<template>
  <div>
    <slot :title="title" />
  </div>
</template>

<script>
export default {
  name: 'hoge',
  data() {
    return {
      title: 'hogehoge'
    }
  }
}
</script>

そして親コンポーネント側で以下のようにします。

<template>
  <hoge>
    <template #title="slotProps">
      {{ slotProps.title }}
    </template>
  </hoge>
</template>

<script>
import hoge from './hoge'
export default {
  name: 'fuga',
  components: {
    hoge
  }
}
</script>

slotPropsは任意の名前でOKです。

名前付きslotが無い場合は#titleとなっているところをv-slot:default (#default)またはv-slotとすれば対応できます。

イベントハンドラ

slotに対してイベントハンドラを直接設定することはできません。

<template>
  <div>
    <slot @click="title = 'hogefuga'" />
  </div>
</template>

<script>
export default {
  name: 'hoge',
  data() {
    return {
      title: 'hogehoge'
    }
  }
}
</script>

↑はできないということです。

こちらでも言及されています。

You cannot listen to events on <slot>. It can end up rendering anything: text, plain element, multiple nodes... the behavior will be unpredictable.

なので、イベントハンドラを設定する場合は親コンポーネントで設定するようにします。

<template>
  <hoge @click="title = 'hogefuga'">
    <template>
      {{ title }}
    </template>
  </hoge>
</template>

<script>
import hoge from './hoge'
export default {
  name: 'fuga',
  components: {
    hoge
  },
  data() {
    return {
      title: 'hogehoge'
    }
  }
}
</script>

ここで詰まってしまって思った以上に時間を取られてしまいました。

まとめ

以上Vueのslotについてまとめてみました。

slotを使うとき若干混乱しがちなので改めて整理してみて良かったと思います。

便利な機能であることは間違いないので上手く活用していきたいです。