この記事は Vue.js #3 Advent Calendar 2018 - Qiitaの16日目の記事です。
TL;DR
Vue.jsには インスタンスプロパティに $attrs
というプロパティがあるのですが、以前ちょっと調べた時にあまり情報が出てこなかったので触れてみたいと思います。
合わせてinheritAttrsというプロパティに触れていきます。
公式のDocumentとしてはAPIドキュメントのvm.$attrs やinheritAttrs、GuideのDisabling-Attribute-Inheritance 辺りの説明となります。
実験したソースコードはこちらです。
$attrsの挙動
親から渡されたプロパティの内、styleとclassが除かれたオブジェクト(形式は { [key: string]: string }
)が格納されています。
と聞いてもあまりピンと来ない気もするので実際のコードです。
component A
<template>
<component-b
class="class-name-sample"
color="red"
type="number"
/>
</template>
component B
<template>
<div>{{ $attrs }}</div>
</template>
AからBへ3つの属性を設定しています。
component Bの {{ $attrs }}
で表示されるデータは { color: 'red', type: 'number' }
となります。
渡されている属性からcss用のclassの属性が(+もし設定していればstyleも)除外されたデータ全てが格納されていることが分かります。
inheritAttrsの挙動
componentは上記そのままからのスタートとします。
上記の場合component Bは展開されると
<div color="red" type="number">{ color: 'red', type: 'number' }</div>
という html を出力します。
component Bにpropsが定義されていない属性を与えるとデフォルトでは生成されるルートの要素に属性が追加されます。
component Bに inheritAttrs: fase
を設定します。
<script>
export default {
inheritAttrs: false
}
</script>
このプロパティを設定すると、展開されるhtmlコードが下記のようになります。
<div>{ color: 'red', type: 'number' }</div>
デフォルトで true
になっている場合 propsに設定されていない値が渡された場合HTML属性として設定されますが、これをfalseにすることでそれを防ぐことができます。
使い所
Base Componentからhtmlへ渡す
公式のサンプルそのままになってしまいますが、inputタグ等属性の設定パターンが多数ある場合に、それを全てpropsを経由しても冗長、かつ大変です。
その場合にこの$attrs/inheritAttrsを使うことで綺麗にinputタグに属性を渡すことができます。
base-input(入力値のv-model等はそっとしてます)
<template>
<label>
{{label}}
<input v-bind="$attrs">
</label>
</template>
使う側
<template>
<base-input label="label name here" type="text" placeholder="placeholder here"/>
</template>
上記で出力されるhtmlは
<label>
label name here
<input type="text" placeholder="placeholder here">
</label>
となります。base-inputが愚直にやるとprops祭りになってしまいますが、この方式であればすっきりしています。
また、作っているうちに他の属性も必要だ、となったときにもそのまま使用することができます。
UI系のライブラリに渡す
例えばElement(UI?)を使う時にライブラリをラップするコンポーネントを作りたいとします。
その時に無数にあるライブラリへ渡すオプションを同様にpropsへ生やしていくのはしんどいため、$attrsを使うと外からそのままライブラリコンポーネントへ流せるようになります。
デメリット
コード上はすっきりする反面で、実際に下のコンポーネント、またはhtmlに対してどの属性が変更されるパターンが存在するのかは分かりにくくなります。
おまけの v-bind
そのまま渡しちゃおう関連で、割と愚直に
<component-x
:props-a="values.a"
:props-b="values.b"
:props-c="values.c"
:props-d="values.d"
:props-e="values.e"
/>
等とやっていて、 ...values
みたいに渡せないのかとか斜めなことを考え、v-bindで普通に丸ごと渡せるやん…と何故か最近気づいたのでおまけです。
<template>
<component-x v-bind="values" />
</template>