Tech

Vue.jsの$attrsとinheritAttrs

この記事は Vue.js #3 Advent Calendar 2018 - Qiitaの16日目の記事です。

TL;DR

Vue.jsには インスタンスプロパティに $attrs というプロパティがあるのですが、以前ちょっと調べた時にあまり情報が出てこなかったので触れてみたいと思います。
合わせてinheritAttrsというプロパティに触れていきます。

公式のDocumentとしてはAPIドキュメントのvm.$attrsinheritAttrs、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>
share