78
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vue #3Advent Calendar 2019

Day 12

Vue.js の inheritAttrs に関する大きな勘違い

Last updated at Posted at 2019-12-12

はじめに

Vue コンポーネントには inheritAttrs というディレクティブがあります。

直訳すると 「属性を引き継ぐ」

まず簡単な例を示します。Vuetify.jsv-btn をラップしたボタンを作ってみましょう。

  • クリックすると消滅する DismissibleButton を作ります。
  • 消滅するまでの時間をミリ秒で timeout で指定できるようにします。
DismissibleButton.vue
<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 に流すかどうかの判定が行われますが,**自分でその処理を書く必要があります。

DismissibleButton.vue
<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 にはあまり頼らないほうがいいのかもしれませんね…

78
35
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
78
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?