JavaScript
vue.js

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


はじめに

子コンポーネントを動的に切り替える必要があり、そのときにそのコンポーネント特有の 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 に以前の値が残り続けるのはなんだか怖いので、なるべく使いたくない機能であると思いました。