Vue 3 のフラグメントについてハマったことの備忘録

Published at: 2022/11/30


Vue 3 から Fragment が導入され、具体的には component のルートノードを複数持てるようになった。

<template>
  <h2>Title</h2>
  <p>Hello, World</p>
</template>

これを活用してこのブログを記述していった際に、ハマったことと、それに付随して Fragment の調査を行ったので、その備忘録。

Fragment の挙動

Fragment は、 component の root が単一ノードでなかったり、 <slot> がテンプレートの直下に置いてある場合に生成される VNode.

<template>
  <slot></slot>
</template>

html (DOM) 上では、 <!--[--><!--]--> のタグによって表現され、その区間が Fragment の中身に対応する。

Fragment では inheritAttrs が強制的に false になる

Vue は元々、 inheritAttrs の機能によって、親コンポーネントから渡されてきた attribute が勝手にそのコンポーネントの根本の element に反映してくれる機能がある。

上記の公式ページにもある通り、 Fragment が利用される場合には、親から見た直下の element が判定できないため、この機能は強制的に off になる。

inheritAttrs: false でできなくなること

inheritAttrs は親コンポーネント側から子供コンポーネントの一番上の element の attribute を指定する機能なので、これによって動作する以下の機能が正しく動かなくなる。

  1. 親から子供コンポーネントの class 属性を指定すること
  2. scoped css の scope を子供(のルート)に適用する
    • こちらの issue を見る限り、 $attrs にも渡らない実装になってしまっているようなので、どちらかというとバグっぽくはある

これらは、例えば scoped css で親コンポーネントにて子供コンポーネントたちの margin を指定するような場合において発生する。

例:

<template>
  <div>
    <!-- ChildComponent は Fragment を出力するコンポーネント -->
    <ChildComponent class="parent__child" />
    <p>foo bar</p>
  </div>
</template>

<style lang="scss" scoped>
// ChildComponent の結果には data-v-* が付与されなかったりして、
// このスタイルがどう頑張っても確実には適用されない問題
.parent__child {
  margin_bottom: 10px;
}
</style>

行った対策

特に scoped css の方は、現状 Fragment を利用する限りにおいては回避手段がなさそうなので、このように scoped css を適用したい場合には、素直に root 要素を ChildComponent の方で用意するような実装にして回避をした。


Tags: vuevue3

Related Articles