Vue2で孫コンポーネントにprops, events, slotsをリレーする
始めに
コンポーネントをラップして少しだけ拡張させたコンポーネントを作るケースが度々あると思いますが、そのときにprops, events, slotsを再定義するのは大変な上、元々のコンポーネントに修正が入ったら修正を追従する必要があります。その辺を楽にするために、受け取ったpropsなどをそのままリレーするような書き方が実はあるのですが、その情報がまとまっていなさそうなのでまとめてみました。
例としてv-data-table
に検索ボックスを合体させてローカルで検索するdata-table-with-search
コンポーネントを作る話にしたいと思います。
それぞれのリレー方法
propsをリレーする
propsを定義しなかったデータは$attrs
に入ってくるので、それをv-bind
で全部流し込むと送ることができます。
<template>
<v-data-table
v-bind="$attrs"
>
</v-data-table>
</template>
<script>
export default Vue.extends({
inheritAttrs: false,
});
</script>
attributesとして認識されたものは何もしないとDOMに表示されてデバッグの邪魔になるので、inheritAttrs: false
にして表示されないようにします。
参考
eventsをリレーする
eventの設定は簡単で、$listeners
で@input="~"
みたいに書いた内容を全部受け取ってくれるので、これをv-on
に入れてあげます。
<template>
<v-data-table
v-bind="$attrs"
+ v-on="$listeners"
>
</v-data-table>
</template>
参考
slotsをリレーする
slotsは$scopedSlots
に入ってくるので、これをループして改めてslotを定義します。
<template>
<v-data-table
v-bind="$attrs"
v-on="$listeners"
>
+ <template v-for="(_, slot) in $scopedSlots" v-slot:[slot]="props">
+ <slot :name="slot" v-bind="props" />
+ </template>
</v-data-table>
</template>
<!-- 親コンポーネントから呼び出し -->
<template>
<data-table-with-search>
<template v-slot:[`item.sex`]="{ item }">
<v-chip
:color="item.sex === '男性' : 'blue' : 'red'"
label
dark
>{{ item.sex }}</v-chip>
</template>
<template v-slot:[`item.url`]="{ item }">
<a
:href="item.url"
target="_blank"
rel="noreferrer noopener"
>{{ item.url }}</a>
</template>
</data-table-with-search>
</template>
<!-- DataTableWithSearchコンポーネントは以下のように展開される -->
<template>
<v-data-table
v-bind="$attrs"
v-on="$listeners"
>
<!--
v-data-tableにある`item.sex`, `item.url`slot情報を受け取って、
改めてこのコンポーネントでも`item.sex`, `item.url`slotを使えるように再定義
-->
<template v-slot:[`item.sex`]="props">
<slot :name="slot" v-bind="props" />
</template>
<template v-slot:[`item.url`]="props">
<slot :name="slot" v-bind="props" />
</template>
</v-data-table>
</template>
補足
今回v-slot:top
に検索ボックスを入れることにしましたが、$scopedSlots
の方でも展開されてしまったらどうなるのかも検証してみました。結論を言うと、後に定義された方が優先されるようで、上書きさせたいかどうかで順番を決めると良いと思いました。
<template>
<v-data-table
v-bind="$attrs"
:search="$data.searchWord"
v-on="$listeners"
>
<!-- 先にv-slot:topを拡張定義する -->
<template v-slot:top>
<v-text-field
v-model="searchWord"
label="Search"
outlined
dense
hide-details
/>
</template>
<!--
$scopedSlotsでもv-slot:topが定義される可能性があり、
定義された場合は後勝ちでこちらが優先される
(上書きされたくない場合は$scopedSlotsのループを先に書く)
-->
<template v-for="(_, slot) in $scopedSlots" v-slot:[slot]="props">
<slot :name="slot" v-bind="props" />
</template>
</v-data-table>
</template>
参考
余談
Vue2には$slots
と$scopedSlots
があるのですが、$slots
の方はv-slot:scope_name="props"
みたいに値を受け取っちゃうと拾えなくなってしまうようです。
終わりに
以上がVue2で孫コンポーネントにpropsなどをリレーさせる方法でした。Vue2は今更感な気はしますが、Vue3も似たようなことをすればリレーできるはずなのでご参考になれれば幸いです。
完成版のサンプルは以下に置きますので、興味がある方は是非みてください。
Discussion