本文へジャンプ

フォールスルー属性

このページは、すでにコンポーネントの基礎を読んでいることを前提にしています。初めてコンポーネントに触れる方は、まずそちらをお読みください。

属性の継承

"フォールスルー属性"とは、あるコンポーネントに渡されたものの、受け取ったコンポーネントの propsemits で明確に宣言されていない属性、または v-on イベントリスナーを指します。よくある例としては、classstyleid 属性などがあります。

コンポーネントが単一のルート要素をレンダリングする時、フォールスルー属性は自動的にルート要素の属性に追加されます。例えば、次のようなテンプレートを持つ <MyButton> コンポーネントがあったとします:

template
<!-- <MyButton> のテンプレート -->
<button>click me</button>

そして、このコンポーネントを使う親が以下です:

template
<MyButton class="large" />

最終的に DOM は以下のようにレンダリングされます:

html
<button class="large">click me</button>

ここで、<MyButton>class を受け入れ可能な props として宣言していません。そのため、class はフォールスルー属性として扱われ、自動的に <MyButton> のルート要素に追加されます。

classstyle のマージ

もし、子コンポーネントのルート要素にすでに classstyle 属性がある場合は、親から継承された classstyle の値にマージされます。先ほどの例の <MyButton> のテンプレートを次のように変更するとします:

template
<!-- <MyButton> の テンプレート -->
<button class="btn">click me</button>

そうすると、最終的にレンダリングされる DOM は、こうなります:

html
<button class="btn large">click me</button>

v-on リスナーの継承

同じルールが v-on イベントリスナーにも適用されます:

template
<MyButton @click="onClick" />

click リスナーは <MyButton> のルート要素、つまりネイティブの <button> 要素に追加されます。ネイティブの <button> がクリックされた時、親コンポーネントの onClick メソッドがトリガーされます。もし、ネイティブの <button> が既に v-on でバインドされた click リスナーを持っている場合、両方のリスナーがトリガーされます。

ネストされたコンポーネントの継承

あるコンポーネントが他の 1 つのコンポーネントをルートノードとしてレンダリングする場合を考えてみましょう。例として、<MyButton> をルートとして <BaseButton> をレンダリングするようにリファクタリングしました:

template
<!-- シンプルに他の 1 つのコンポーネントをレンダリングする <MyButton/> のテンプレート -->
<BaseButton />

この時、<MyButton> が受け取ったフォールスルー属性は、自動的に <BaseButton> に転送されます。

以下の点に注意してください:

  1. 転送された属性には、<MyButton> が props として宣言した属性や、宣言したイベントの v-on リスナーは含まれません。言い換えると、宣言した props とリスナーは <MyButton> によって "消費" されています。

  2. 転送された属性は、 <BaseButton> が宣言していれば、props として受け取ることができます。

属性の継承の無効化

コンポーネントに自動的な属性の継承をさせたくない場合は、コンポーネントのオプションで inheritAttrs: false を設定できます。

3.3 以降では、<script setup> で直接 defineOptions を使用することもできます:

vue
<script setup>
defineOptions({
  inheritAttrs: false
})
// セットアップのロジック
</script>

属性の継承を無効にする一般的なシナリオは、ルートノード以外の要素に属性を適用する必要がある場合です。 inheritAttrs オプションを false に設定することで、フォールスルー属性を適用する場所を完全に制御できます。

これらのフォールスルー属性は、テンプレート内の式で $attrs として直接アクセスできます:

template
<span>Fallthrough attributes: {{ $attrs }}</span>

$attrs オブジェクトには、コンポーネントの propsemits オプションで宣言されていないすべての属性(例えば class, style, v-on リスナーなど)が含まれます。

備考:

  • props とは異なり、フォールスルー属性は JavaScript では元のケーシングを保持します。したがって、 foo-bar のような属性は $attrs['foo-bar'] としてアクセスされる必要があります。

  • @click のような v-on イベントリスナーは、オブジェクトで $attrs.onClick という関数として公開されます。

前のセクションで紹介した <MyButton> コンポーネントの例では、スタイリングのために実際の <button> 要素を <div> でラップする必要がある場合があります:

template
<div class="btn-wrapper">
  <button class="btn">click me</button>
</div>

classv-on リスナーなどのすべてのフォールスルー属性を、外側の <div> ではなく、内側の <button> に適用されるようにしたいです。これは、 inheritAttrs: falsev-bind="$attrs" で実現できます:

template
<div class="btn-wrapper">
  <button class="btn" v-bind="$attrs">click me</button>
</div>

引数なしの v-bind はオブジェクトのすべてのプロパティをターゲット要素の属性としてバインドすることを覚えておきましょう。

複数のルートノードでの属性継承

ルートノードが 1 つのコンポーネントと異なり、複数のルートノードを持つコンポーネントは、自動的に属性をフォールスルーするふるまいがありません。 $attrs が明示的にバインドされていない場合は、実行時に警告が出ます。

template
<CustomLayout id="custom-layout" @click="changeValue" />

もし <CustomLayout> が以下のようなマルチルートのテンプレートを持っている場合、 Vue はどこにフォールスルー属性を適用すればよいか分からないため、警告されます:

template
<header>...</header>
<main>...</main>
<footer>...</footer>

警告は $attrs が明示的にバインドされている場合は抑制されます:

template
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>

JavaScript 内でフォールスルー属性にアクセスする

必要であれば、<script setup> 内で useAttrs() API を使用してコンポーネントのフォールスルー属性にアクセスできます:

vue
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

もし <script setup> を使用していない場合、 attrssetup() コンテキストのプロパティとして公開されます:

js
export default {
  setup(props, ctx) {
    // フォールスルー属性が ctx.attrs として公開される
    console.log(ctx.attrs)
  }
}

ここで attrs オブジェクトは常に最新のフォールスルー属性を反映していますが、リアクティブではないことに注意してください(パフォーマンス上の理由です)。ウォッチャーを使ってその変更を監視することはできません。リアクティビティーが必要であれば、 props を使ってください。または、 onUpdated() を使用して、更新されるたびに最新の attrs による副作用を実行することもできます。

必要であれば、$attrs インスタンスプロパティを介して、コンポーネントのフォールスルー属性にアクセスできます:

js
export default {
  created() {
    console.log(this.$attrs)
  }
}
フォールスルー属性が読み込まれました