はじめに
Vue コンポーネントには inheritAttrs
というディレクティブがあります。
直訳すると 「属性を引き継ぐ」。
まず簡単な例を示します。Vuetify.js の v-btn をラップしたボタンを作ってみましょう。
- クリックすると消滅する
DismissibleButton
を作ります。 - 消滅するまでの時間をミリ秒で
timeout
で指定できるようにします。
<template>
<v-btn v-if="visible" @click="hide">
<slot />
</v-btn>
</template>
<script>
export default {
props: {
timeout: {
type: Number,
default: 0,
},
},
data() {
return {
visible: true,
};
},
methods: {
hide() {
setTimeout(() => {
this.visible = false;
}, this.timeout);
},
},
};
</script>
<dismissible-button :timeout="1000">Click Me</dismissible-button>
- スロットとしてボタンテキストを受け取り,それをそのまま
<v-btn>
に受け流し。 - 可視性の制御を加えるちょっとした実装を書きました。
inheritAttrs
の動作
ではここで,
「<v-btn>
に to
プロパティと nuxt
プロパティを渡したい!」
というニーズが発生したとします。
ここで挙げられている解決策の
<v-btn to="/path/to/link" nuxt>リンク</v-btn>
これですね。これをラップした <dismissible-button>
において,できるだけ再利用性の高い形で <v-btn>
に受け流します。
<dismissible-button :timeout="1000" to="/path/to/link" nuxt>リンク</dismissible-button>
このような使い方をすることを考えます。
true
のとき (デフォルト)
何も指定していないときはこれが適用されます。
上記の場合 component B は展開されると
<div color="red" type="number">{ color: 'red', type: 'number' }</div>
という html を出力します。
component B に props が定義されていない属性を与えるとデフォルトでは生成されるルートの要素に属性が追加されます。
これを読んだ僕は,このようなレンダリング結果を期待しました。
<v-btn>
がコンポーネントのルート要素だから,勝手にそこに引き継いでくれる!!!
と。
<template>
<v-btn v-if="visible" @click="hide" :to="to" :nuxt="nuxt">
<slot />
</v-btn>
</template>
でも実際はそこに展開されず,なんと <v-btn>
より更に下のHTML要素の <button>
に渡っていまっていました…
<button to="/path/to/link" nuxt=""></button>
こういうことなのだ…
(この図を目に焼き付けて帰ってください)
端的に言葉で表現すると
「inheritAttrs
で下位コンポーネントに流すと『props
として定義されていたら $props
に流す』という処理をスキップして全部 $attrs
に流す」
ということになります。これは初見殺しだ…
false
のとき
inheritAttrs: false
にすると,暗黙的な受け流しは行われなくなります。**明示的に v-bind
で受け流した場合は $props
に流すかどうかの判定が行われますが,**自分でその処理を書く必要があります。
<template>
<v-btn v-if="visible" @click="hide" v-bind="$attrs"> <!-- ← これを追加! -->
<slot />
</v-btn>
</template>
<script>
export default {
inheritAttrs: false, // ← これを追加!
props: {
timeout: {
type: Number,
default: 0,
},
},
data() {
return {
visible: true,
};
},
methods: {
hide() {
setTimeout(() => {
this.visible = false;
}, this.timeout);
},
},
};
</script>
v-bind="$attrs"
に関して説明すると,以下の2つの記述は等価となります。
<v-btn v-if="visible" @click="hide" v-bind="$attrs">
<v-btn v-if="visible" @click="hide" :to="$attrs.to" :nuxt="$attrs.nuxt">
実際には $attrs
はこれに限らないので,数が増えても自動的にすべて受け流される v-bind="$attrs"
が優秀だと言えますね。
まとめ
大事なことなのでもう一度。`
inheritAttrs
はすべて$attrs
に流す- 明示的に
v-bind="$attrs"
で流すと$props
と$attrs
の振り分けが行われる
基本的に inheritAttrs: true
にはあまり頼らないほうがいいのかもしれませんね…