ただたんに、親コンポーネントから孫コンポーネントにプロパティを渡したりslotの機能を利用するような、中継用の子コンポーネントがほしくなることがよくあります。
なのに、なかなか書き方を覚えられないからメモ。
結論からいうと、「TheContainer」というコンポーネントがあるとして、最もシンプルに作るなら下記のようになると思います。
<script setup lang="ts">
import TheContainer from "./TheContainer.vue";
</script>
<template>
<TheContainer msg="">
<template v-for="(slot, name) in $slots" #[name]="data">
<slot :name="name" v-bind="data"></slot>
</template>
</TheContainer>
</template>
これで、「TheContainer」の機能を親コンポーネントを利用できます。
Vue2の時は、イベントはルートまでたどらないようになっていないため、「v-on=”$listeners”」という指定も必要だったと思いますが、Vue3では必要ないようです。
試しに作ってみた孫コンポーネントと親コンポーネントは下記
孫コンポーネント(TheContainer)
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps<{
msg: string;
headerMsg?: string;
footerMsg?: string;
}>();
const defaultMsg = computed(() => {
return {
header: props.headerMsg ?? "ヘッダー",
msg: props.msg,
footer: props.footerMsg ?? "フッター",
};
});
</script>
<template>
<div class="container">
<header @click="$emit('header-click')">
<slot name="header" :msg="defaultMsg">{{ defaultMsg.header }}</slot>
</header>
<main @click="$emit('main-click')">
<slot :msg="defaultMsg">{{ defaultMsg.msg }}</slot>
</main>
<footer @click="$emit('footer-click')">
<slot name="footer" :msg="defaultMsg">{{ defaultMsg.footer }}</slot>
</footer>
</div>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
height: 100vh;
}
.container header {
height: 50px;
background-color: aqua;
}
.container main {
flex: 1;
background-color: burlywood;
}
.container footer {
height: 100px;
background-color: darkseagreen;
}
</style>
親コンポーネント
<script setup lang="ts">
import TheRelay from "../components/relay/TheRelay.vue";
const footerClick = () => {
console.log("Footer Click");
};
</script>
<template>
<TheRelay msg="メイン" @footer-click="footerClick">
<template #header>タイトル</template>
<template #default="{ msg }">メインメッセージ:{{ msg.msg }}</template>
</TheRelay>
</template>
フッターをクリックすると、コンソールに「Footer Click」と表示されました。

プロパティの指定を明示的にしたい場合は、中継用コンポーネントから参照する孫コンポーネントに「v-bind=”$attrs”」と指定して、setupをつけないscriptタグ内で「inheritAttrs: false」を返すようにします。その場合、「v-bind=”$attrs”」より後にプロパティを指定すると、値がその値で上書きされます。
中継用コンポーネント
<script lang="ts">
export default {
inheritAttrs: false,
};
</script>
<script setup lang="ts">
import TheContainer from "./TheContainer.vue";
</script>
<template>
<TheContainer v-bind="$attrs" msg="aiueo">
<template v-for="(slot, name) in $slots" #[name]="data">
<slot :name="name" v-bind="data"></slot>
</template>
</TheContainer>
</template>
slotも中継用コンポーネントでしたものを上書きしたいとなることがあると思いますが、ただたんに下記のようにslotの名前のtemplateタグを指定しても上書きされないようです(先に指定してもダメ)。
<template>
<TheContainer v-bind="$attrs" msg="aiueo">
<template v-for="(slot, name) in $slots" #[name]="data">
<slot :name="name" v-bind="data"></slot>
</template>
<template #header><button>ログアウト</button></template>
</TheContainer>
</template>
slotを上書きしたい場合、slotsを書き換える必要がありそう。いろいろ試してみたら、下記のようslotsからheader以外を配列に入れて、その配列をv-forで回すことでできました。
<script lang="ts">
export default {
inheritAttrs: false,
};
</script>
<script setup lang="ts">
import { computed, useSlots } from "vue";
import TheContainer from "./TheContainer.vue";
const slots = useSlots();
const relaySlot = computed(() => {
const slotNames = [];
for (const slotName in slots) {
if (slotName !== "header") {
slotNames.push(slotName);
}
}
console.log("slotNames", slotNames);
return slotNames;
});
</script>
<template>
<TheContainer v-bind="$attrs" msg="aiueo">
<template v-for="name in relaySlot" #[name]="data">
<slot :name="name" v-bind="data"></slot>
</template>
<template #header="data">
<slot name="header" v-bind="data"></slot>
<button>ログアウト</button>
</template>
</TheContainer>
</template>
ここはもう少しうまいことできないかなとは思う。



コメント