Dynamic Componentでattributeを動的に展開するときに調べたこと（Vue.js）

# はじめに
子コンポーネントを動的に切り替える必要があり、そのときにそのコンポーネント特有の props を  attribute に展開する必要がありました。
その方法と検証中にみつけた、やっておくと良さそうなことを書きます。

# わかったこと
- 動的コンポーネントで動的に attribute を展開させるには`v-bind=""`で行う。
- 子コンポーネントは`inheritAttrs: false`にしていないと危険。
 - inheritAttrsについては[Vue.js公式](https://jp.vuejs.org/v2/api/#inheritAttrs)からどうぞ。
 - 動的コンポーネントについては[Vue.js公式](https://jp.vuejs.org/v2/guide/components-dynamic-async.html)からどうぞ。

今回使用したコードは[GitHub](https://github.com/ishiyama0530/vue-dynamic-component-sample)に置いてます。

# 内容
```component/PrefixA.vue
/* 子コンポーネントです。props は mark のみです。 */
<template>
  <span>(A){{ mark }}</span>
</template>
<script>
export default {
  props: ["mark"]
};
</script>

```
```component/PrefixB.vue
/* こちらも子コンポーネントです。こちらの props は mark と color の2つです。 */
<template>
  <span :style="{ color }">(B){{ mark }}</span>
</template>
<script>
export default {
  props: ["mark", "color"]
};
</script>

```
```component/Paragraph.vue
/* props でコンポーネントを受け取って prefix として表示する段落表現のコンポーネントです。 */
<template>
  <p>
    <component :is="prefix.component" v-bind="prefix.props"></component>
    <slot></slot>
  </p>
</template>
<script>
export default {
  props: {
    prefix: {
      validator: v => {
        return v.component && v.props;
}}}};
</script>
```
props の prefix では動的に描画するコンポーネントとそれにセットする props を受け取ります。

また、コンポーネントと props をペアで受け取ることを明記するため、validator も記述しています。TypeScriptを使用している場合はinterfaceで定義してあげるとより解りやすいかと。

```vue
<component :is="prefix.component" v-bind="prefix.props"></component>
```

`<component :is=""></component>`で:isの値にコンポーネントをセットすることで動的にコンポーネントを描画することができます。また`v-bind=""`に props のオブジェクトをセットすると、受け渡された props が展開されます。

今回は App.vue で定義している Paragraph.vue の props を PrefixA.vue と PrefixB.vue で動的に切り替えたいと思います。

```App.vue
<template>
  <div>
    <Paragraph :prefix="prefix">sample</Paragraph>
    <button @click="onclickA">switch_a</button>
    <button @click="onclickB">switch_b</button>
  </div>
</template>

<script>
import Paragraph from "./components/Paragraph.vue";
import PrefixA from "./components/PrefixA.vue";
import PrefixB from "./components/PrefixB.vue";

export default {
  name: "app",
  components: {
    Paragraph,
    PrefixA
  },
  data: () => {
    return {
      prefix: {
        component: PrefixA,
        props: {
          mark: "?"
    }}};
  },
  methods: {
    onclickA() {
      this.prefix.component = PrefixA;
      this.prefix.props.mark = "+";
    },
    onclickB() {
      this.prefix.component = PrefixB;
      this.prefix.props.mark = "-";
      this.prefix.props.color = "red";
}}};
</script>
```
最初は「?」を表示、 switch_a ボタンを押すと「+」に switch_b を押すと赤色で「-」に切り替わります。

```html
<p><span style="color: red;">(B)-</span>sample</p>
```
こんな感じで展開されています。

# これだけじゃ危ない
これの実行結果を見てみると…
![bad.gif](https://qiita-image-store.s3.amazonaws.com/0/62209/19735c1a-5cb7-6dee-9dec-455f074ccbc4.gif)
B → A に切り替えたときにフォールスローの機能で

```vue
<p><span color="red">(A)+</span>sample</p>
```
`color="red"`という想定外の attribute が残ってしまいます。
propsの値がどうなっているかというと
![dev.gif](https://qiita-image-store.s3.amazonaws.com/0/62209/241162b7-2c6b-9047-e7c3-7fb2c47c8070.gif)
こんな感じで props に `color: red`が残り続けています。知らない間にバグってそうですね。

# inheritAttrs: false にすれば抑制できた（version2.4.0から）
![ok.gif](https://qiita-image-store.s3.amazonaws.com/0/62209/ae387946-5c39-a66e-2341-f4233f48ce30.gif)

これを子コンポーネントに設定することで親から子への attribute のフォールスローが抑制されます。
> 注意: このオプションは class と style のバインディングには効果がありません。

こんな感じです。（component/PrefixB.vueも同様に）

```component/PrefixA.vue
<template>
  <span>(A){{ mark }}</span>
</template>
<script>
export default {
  inheritAttrs: false, // 👈
  props: ["mark"]
};
</script>

```
# まとめ
独立したコンポーネントが別コンポーネントのために`inheritAttrs: false`を設定しないといけないのが、コンポーネント間を意識してのことなので、個人的にはあまり気が進みません。

何もしないと inheritAttrs は ture の状態ですが、フォールスローを許可していても嬉しいことはあまりなく、バグの原因にもなり得えると思います。私の経験ではフォールスローの恩恵を得るのは class と style くらいなので、inheritAttrs をfalse に設定したとしてもその2つで使用できるのであればとくに困りません。

[こちらの記事](http://tic40.hatenablog.com/entry/2018/07/25/080000)にもありますが、もし Vue の基底クラスや mixin を設けるのであれば副作用の抑制のためにも通常を`inheritAttrs: false`にするのが良いと思います。

フロントエンドの大規模な開発では、配列にコンポーネントを詰めて受け渡すようなこともあると思います。ですが props に以前の値が残り続けるのはなんだか怖いので、なるべく使いたくない機能であると思いました。