LoginSignup
13
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-17

はじめに

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

わかったこと

  • 動的コンポーネントで動的に attribute を展開させるにはv-bind=""で行う。
  • 子コンポーネントはinheritAttrs: falseにしていないと危険。
    • inheritAttrsについてはVue.js公式からどうぞ。
    • 動的コンポーネントについてはVue.js公式からどうぞ。

今回使用したコードはGitHubに置いてます。

内容

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で定義してあげるとより解りやすいかと。

<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 を押すと赤色で「-」に切り替わります。

<p><span style="color: red;">(B)-</span>sample</p>

こんな感じで展開されています。

これだけじゃ危ない

これの実行結果を見てみると…
bad.gif
B → A に切り替えたときにフォールスローの機能で

<p><span color="red">(A)+</span>sample</p>

color="red"という想定外の attribute が残ってしまいます。
propsの値がどうなっているかというと
dev.gif
こんな感じで props に color: redが残り続けています。知らない間にバグってそうですね。

inheritAttrs: false にすれば抑制できた(version2.4.0から)

ok.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つで使用できるのであればとくに困りません。

こちらの記事にもありますが、もし Vue の基底クラスや mixin を設けるのであれば副作用の抑制のためにも通常をinheritAttrs: falseにするのが良いと思います。

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

13
8
0

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
13
8