vue.js: functional componentとscoped css
最近はFF14をしたり、某社でフロントやphpを書いたりしています。 Vueをメインに書いていますが、その中でちょっとハマった点があったのでまとめがてら記載します。
vue (2.5.17) にはvueloaderのscoped cssをfunctional componentでうまく扱えない挙動があります。 その対策をメモ的に書いておきます。
現象
vueにはインスタンスを持たない軽量なコンポーネントとしてfunctionalというコンポーネントを作ることができるようなりました。 これは画期的で、大量に使うようなbaseコンポーネントを負荷を恐れず簡単に分離し大量に用いることができます。
そこで私はいろいろと共通化できる処理を細かい単位でfunctional-componentとして分離して共通化していっているのですが、その過程でちょっと困ったことになりました。 functionalでない普通のコンポーネントからfunctionalなコンポーネントを利用するとscoped cssやクラスがうまく付きませんでした。
まとめると、
- v-bindでない記法で付与した静的なクラスは, v-bind="data.attrs"でバインドすることはできません。
- functionalでないコンポーネント内でfunctionalなコンポーネントを利用して、functionalでないコンポーネント側で掛けたscoped cssが反映されません。
です。
コードにすれば、
<template functional> <span v-bind="data.attrs">{{ props.text }}</span> </template> <script> export default { name: 'functional-component', props: { text: String } } </script>
のようなfunctilnal componentに対して
<template> <div> <functilnal-component text='hoge' class='red'/> </div> <template> <style scoped> .hoge { color: red; } </style>
をしたとき文字が赤色にならないということです。
原因
静的なクラスとして与えたhogeはfunctional-componentでは通常コンポーネントで行われるクラスのマージが行われないため,functional-componentのルート要素spanに付与されません。
vueのscoped cssは
data-v-xxxx
のような属性をrender時に全ての要素に付与し, cssを属性セレクタ付きに書き換えることで実現していますが, 上記のように利用した場合 functional-component側でv属性はマージされず, functional-component側だけのv属性がDOMに付与されるため, cssセレクタに一致せずスタイルが適用されません。
これはclassや属性をマージする一般的なhowtoである、functionalコンポーネント側でv-bind=data.attrs
のようなことをしても解決しません。
対策
functionalコンポーネントでは(というよりvnodeでは)静的なクラスとして付与された文字列は
data.staticClass
で参照可能ですそのためv-bind:class="data.staticClass"
とすることで普通に渡したクラスをfunctionalコンポーネント側の要素に付与することができます。これは厄介ですがvue-template-loaderによって実現されるscopedCssに使うscopeId (data-v-xxx)はvueインスタンスの
$options._scope_id
で取得することができます。 つまりこれをバインドしてやればfunctional-compnent側でdata-v-xxxな値をマージすることができます。
これらを合わせると上記の問題を解決するfunctional-componentのtemplateは以下になります。
<template functional> <span v-bind="(() => { const res = data.attrs; /* scopeIdをマージする */ res[`${parent.$options._scopeId}`] = ''; return res; })()" :class="data.staticClass" >{{ props.text }}</span> </template>
このようにv-bindとclassbindingを書いておくと, functional-componentを自然に普通のコンポーネントから利用できるようになります。
v-bindの中が黒魔術
他にはcss-moduleを利用するなどのscopedcssに頼らない根本的な解決策がありますが、私はscoped-cssがシンプルでvueらしく好きなので一旦これで進むことにしました。