💬

Vue.jsでRest Typeを使って、propsの一部をそのまま子コンポーネントに流す

2024/06/14に公開

あるコンポーネントをあるコンポーネントでラップして使いたいケースがたまにあります。

単純にAコンポーネントのエイリアスとしてBコンポーネントを作る場合、$propsをv-bindに割り当てればすみます。

types.ts:

export interface AProps {
  a: string;
  b: number;
  c?: boolean;
}

A.vue:

<script setup lang="ts">
import { AProps } from "./types";

const props = defineProps<AProps>();
</script>

<template>
  <div>{{ props.a }}</div>
  <div>{{ props.b }}</div>
  <div>{{ props.c }}</div>
</template>

B.vue:

<script setup lang="ts">
import { AProps } from "./types";
import A from "./A.vue";

const props = defineProps<AProps>();
</script>

<template>
  <A v-bind="$props" />
</template>

しかし、BコンポーネントでBコンポーネント独自のpropsを受け取りつつ、AコンポーネントのpropsはAコンポーネントに流したい場合はどうすればよいでしょうか。

単純な解決として、Aコンポーネントで使うpropsを一つ一つ記述して渡します。

<script setup lang="ts">
import { AProps } from "./types";
import A from "./A.vue";

const props = defineProps<AProps & { d: string }>();

</script>

<template>
<div>
  {{ props.d }}
  <B :a="props.a" :b="props.b" :c="props.c" />
</div>
</template>

しかしこれではAコンポーネントに変更が入った場合に追従がめんどうです。

分割代入で、Bコンポーネント独自のpropsを受け取りつつ、rest typeでAコンポーネントのpropsを受け取るという手法が思い浮かびます。

import { AProps } from "./types";
import A from "./A.vue";

const props = defineProps<AProps & { d: string }>();

const { d, ...rest } = props;

しかしこれではリアクティビティが失われてしまいます。

そこでcomputedを使います。computedによって、Bに指定されたpropが変更された場合でもAは変更されます。

<script setup lang="ts">
import { computed } from "vue";
import { AProps } from "./types";
import A from "./A.vue";

const props = defineProps<AProps & { d: string }>();

const rest = computed(() => {
  const { d: _, ...rest } = props;
  return rest;
});
</script>

<template>
<div>
  {{ props.d }}
  <A v-bind="rest"></A>
</div>
</template>

この方法なら、Aのpropsに変更が入った場合でも自動でBは追従することができます。ただし、Bコンポーネントのpropsが増えた場合にrestの取り出しが面倒です。なにか別の良いやり方があればコメントお待ちしています。

Discussion