Vue3のコンポーネントの「フォールスルー属性」、属性・プロパティの継承
作りたかったもの
今回VueのUIフレームワーク「Vuetify」を使用しました。
Vuetifyを使用したうえで、上画像のような、マウスを重ねると色が変わり、Tipが表示されるようなボタンが欲しかったので、VuetifyのVBtnを内包した新しいコンポーネント(ここではIconBtn)を自作しました。
課題
作成したのはよかったのですが以下のようなエラーが出ました。
Extraneous non-props attributes were passed to component but could notbe automatically inherited because component renders fragment or text root nodes.
「class」や「density」属性が設定されてるけど無視されてる(適用されない)よー、みたいなエラーです。
この時のコード
このエラーが出た時のコードを以下に示します。
<script lang="ts">
export default defineComponent({
layout: false,
name: "IconBtn",
// inheritAttrs: false,
});
</script>
<script setup lang="ts">
interface Props {
tips?: string,
variant?: "flat" | "text" | "elevated" | "tonal" | "outlined" | "plain",
color?: string,
icon: string,
size?: string | number,
disabled?: boolean,
hover?: boolean,
}
const Props = withDefaults(defineProps<Props>(), {
tips: undefined,
variant: 'text',
color: 'primary',
icon: 'mdi-home',
size: "default",
disabled: false,
hover: true,
});
</script>
<template>
<v-hover #default="{ isHovering, props }">
<v-btn v-bind="props" icon="" :variant="variant">
<v-tooltip v-if="tips" activator="parent" :text="tips" location="top"/>
<v-icon :icon="icon" :color="hover ? (isHovering ? color : 'gray') : color" :size="size"/>
</v-btn>
</v-hover>
</template>
<template>
<MyIconBtn icon="mdi-content-save" :color="dbUpdate ? 'error' : 'gray'" :hover="false" class="mx-2" density="compact"
:disabled="permission>3" @click="saveDB" tips="DB設計保存"/>
</template>
さて、何が起こっているのでしょうか。
「フォールスルー属性」とは
その前に、Vue3における「フォールスルー属性」についてです。
詳細は以下のページを参照していただければと思いますが、ざっくり説明しますと、コンポーネントの呼び出し(ここではindex.vueでの記述)時にコンポーネントに設定された属性のうち、子コンポーネント(ここではIconBtnコンポーネント)の「props」に含まれていない属性は、自動的に子コンポーネントのルート要素に継承される、というものです。
つまり、例えば以下のような何も属性を持たないボタンコンポーネントを作成したとして、
<template>
<button>BUTTON</button>
</template>
以下のように呼び出したとすると、
<template>
<MyBtn @click="some" class="btn" :hoge="fuga"/>
</template>
実際にレンダリング(実質)されるのは、「@click」や「class」「hoge」を継承(フォールスルー)した以下のようなコンポーネントということになります。
<template>
<button @click="some" class="btn" :hoge="fuga">BUTTON</button>
</template>
原因
つまり、今回の事象の原因は、子コンポーネントのルート要素が「VBtn」ではなく、「hover」を検知するためのヘルパー的コンポーネント「VHover」であることです。
こいつは言ってしまえば実態が無いので、実際にレンダリングされるわけではなく、呼び出しもとで設定した「class」や「density」の受取先がない、ということなのです。
解決策
「$attrs」をbindして親で設定した属性を継承する
「$attrs」を使用することで、親コンポーネントで設定した属性にアクセスすることができます。
この「$attrs」を「v-bind」に設定することで、「フォールスルー属性」を全て任意の要素に継承できます。
<template>
<div>
<v-btn v-bind="$attrs" icon="" :variant="variant">
<v-tooltip v-if="tips" activator="parent" :text="tips" location="top"/>
<v-icon :icon="icon" :color="hover ? (isHovering ? color : 'gray') : color" :size="size"/>
</v-btn>
</div>
</template>
ただし、このままではルート要素である「div」にも「フォールスルー」が発生してしまうので、「inheritAttrs: false」を設定して止めます。
<script lang="ts">
export default defineComponent({
layout: false,
name: "IconBtn",
inheritAttrs: false,
});
</script>
以上です。
Vueは使いこなすには結構大変ですな。
Vueもきちんとドキュメントを読まなくてはいけないフレームワークになってきたようだ。
Discussion