Vue.jsで孫コンポーネントのスロットにテンプレートを渡すには 

# やりたいこと

親 -> 子 -> 孫へとスロット経由でテンプレートを渡したい。
固定名称・固定数のスロットではなく任意の・全部のテンプレートを渡したい。

* `<ParentComponent>`: スロット経由でテンプレートを`ChildComponent`に渡す。
* `<ChildComponent>`: `ParentComponent`からスロット経由でテンプレートを受け取る。受け取ったテンプレートはスロット経由で`<GrandChild>`に渡す。
* `<GrandChild>`: `ChildComponent`からスロット経由でテンプレートを受け取る。

# 要点

1. [`#[スロット名]`の記法で、動的に受け渡しスロット名を指定することが可能。](https://jp.vuejs.org/v2/guide/components-slots.html#動的なスロット名)
2. 受け取った全てのスロットは[`$slots`](https://jp.vuejs.org/v2/api/#vm-slots) __もしくは[`$scopedSlots`](https://jp.vuejs.org/v2/api/#vm-scopedSlots)で__ 受け取ることが可能。太字の箇所がハマりやすい。
3. `<slot :name="スロット名"></slot>`のような記法で、動的な名称のスロットからテンプレートを受け取ることが可能。
4. __[スロットプロパティ](https://jp.vuejs.org/v2/guide/components-slots.html#スコープ付きスロット)を親から子へ受け渡すようにする__。

# なぜそんなことをしようと思ったのか

|解説中の役割|実際に直面した課題|
|:--|:--|
|`ParentComponent`|ページ|
|`ChildComponent`|[b-table](https://bootstrap-vue.js.org/docs/components/table)に色々機能を足したりボタンを足したりした便利で汎用的な表|
|`GrandChild`|b-table|

こんな感じにしたかったため。要はb-tableの各列のテンプレートをページからスロット経由で指定したかった。

# 解決方法(本題)

`ParentComponent.vue`

```vue
<template>
  <ChildComponent>
    <template #myslot1="data">
      {{data.value}} 円
    </template>
    <template #myslot2="data">
      {{data.value}} メートル
    </template>
  </ChildComponent>
</template>
```

`ChildComponent.vue`

```vue
<template>
  <GrandChild>
    <template v-for="(slotContent, slotName) of $scopedSlots" #[slotName]="data">
      <slot :name="slotName" v-bind="data"></slot>
    </template>
  </GrandChild>
</template>
```

* `v-for`で __`$scopedSlots`の内容を取得する。__
  * スコープ付きスロットと通常のスロットでは変数名が異なる。スコープ付きの場合`$slot`からは取得できないことに注意。
  * 自分的にはJavaScriptのfor-of文あまり使わなかったのでここもつまづきポイント。
  * for-ofでスロット名とスロット内容の両方を受け取るが、順番もつまづきポイント。`(slotContent, slotName)`の順。
* `#[slotName]="data"`では、(1)動的にスロット名指定する・(2)スロットプロパティを与える の2つを同時にやっている。
  * `#[slotName]`の部分で動的にスロット名を指定する。`slotName`はv-forで取得したスロット名。
  * `="data"`の部分でスロットプロパティを与える。スロットプロパティはこの`<template>`要素の中で使用できる。
* `<slot>`で、`ChildComponent`が受け取ったスロットをデータバインドする。
  * `:name="slotName"`で受け取ったスロット名を指定する。今回は動的にスロット名を受け取ったので、v-forで取得したスロット名を指定。
  * __`v-bind="data"`で、スロットプロパティの値をデータバインドする。__
    * `v-bind=""`は「なんでも全部」データバインドするための記法で、スロット以外でも一般的に使用する。


#　もうちょいちゃんとしたサンプル

`MyPage.vue`

ParentComponentに相当

```vue
<template>
  <div>
    <MyTable :items="items">
      <template #weight="data">
        {{ data.value }}kg
      </template>
      <template #reps="data">
        {{ data.value }}回
      </template>
      <template #sets="data">
        {{ data.value }}セット
      </template>
    </MyTable>
  </div>
</template>

<script>
import MyTable from "./MyTable.vue";

export default {
  name: "MyPage",
  data: function() {
    return {
      items: [
        {
          name: "ベンチプレス",
          weight: 60,
          reps: 12,
          sets: 3
        },
        {
          name: "スクワット",
          weight: 60,
          reps: 10,
          sets: 3
        },
        {
          name: "ショルダープレス",
          weight: 30,
          reps: 11,
          sets: 4
        }
      ]
    };
  },
  components: {
    MyTable
  }
};
</script>
```

`MyTable.vue`

ChildComponentに相当

```vue
<template>
  <div>
    <b-table :items="items">
      <template v-for="(slot, name) of $scopedSlots" #[name]="data">
        <slot :name="name" v-bind="data"></slot>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  name: "MyTable",
  props: {
    items: Array
  }
};
</script>

```

これで`MyTable.vue`は親コンポーネントからスロット経由で受け取った任意のテンプレートを、全部`b-table`に丸投げできるようになった。めでたしめでたし。

# 参考

* https://github.com/vuejs/vue/issues/6996
* https://github.com/vuejs/vue/issues/6615
* [Vue.js v2.6からの名前付きslotの書き方](https://tomatoaiu.hatenablog.com/entry/2019/04/07/121942)