ただたんに、親コンポーネントから孫コンポーネントにプロパティを渡したり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>
ここはもう少しうまいことできないかなとは思う。



コメント