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 を指定する機能なので、これによって動作する以下の機能が正しく動かなくなる。
- 親から子供コンポーネントの
class
属性を指定すること - scoped css の scope を子供(のルート)に適用する
- こちらの issue を見る限り、
$attrs
にも渡らない実装になってしまっているようなので、どちらかというとバグっぽくはある
- こちらの issue を見る限り、
これらは、例えば 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 の方で用意するような実装にして回避をした。