はじめに
子コンポーネントを動的に切り替える必要があり、そのときにそのコンポーネント特有の props を attribute に展開する必要がありました。
その方法と検証中にみつけた、やっておくと良さそうなことを書きます。
わかったこと
- 動的コンポーネントで動的に attribute を展開させるには
v-bind=""
で行う。 - 子コンポーネントは
inheritAttrs: false
にしていないと危険。 - inheritAttrsについてはVue.js公式からどうぞ。
- 動的コンポーネントについてはVue.js公式からどうぞ。
今回使用したコードはGitHubに置いてます。
内容
/* 子コンポーネントです。props は mark のみです。 */
<template>
<span>(A){{ mark }}</span>
</template>
<script>
export default {
props: ["mark"]
};
</script>
/* こちらも子コンポーネントです。こちらの props は mark と color の2つです。 */
<template>
<span :style="{ color }">(B){{ mark }}</span>
</template>
<script>
export default {
props: ["mark", "color"]
};
</script>
/* 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 で動的に切り替えたいと思います。
<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>
こんな感じで展開されています。
これだけじゃ危ない
これの実行結果を見てみると…
B → A に切り替えたときにフォールスローの機能で
<p><span color="red">(A)+</span>sample</p>
color="red"
という想定外の attribute が残ってしまいます。
propsの値がどうなっているかというと
こんな感じで props に color: red
が残り続けています。知らない間にバグってそうですね。
inheritAttrs: false にすれば抑制できた(version2.4.0から)
これを子コンポーネントに設定することで親から子への attribute のフォールスローが抑制されます。
注意: このオプションは class と style のバインディングには効果がありません。
こんな感じです。(component/PrefixB.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 に以前の値が残り続けるのはなんだか怖いので、なるべく使いたくない機能であると思いました。