やりたいこと
子コンポーネントのスタイルを親側からちょっとだけ変えたい!
というのが今回の発端です。
共通で使っているコンポーネントの背景色だけをそれぞれ変えたい、でもそれ以外は全部同じでいいから別コンポーネントにするのもなあ…何か良い方法無いかなーって考えてました。
要は子コンポーネントのスタイルを親コンポーネントから操作するということなのですが、意外と情報量が少なかったので備忘録を兼ねて記述しておきます。
方法自体は後述の通りいくつかありますが、個人的に一番気に入っている親コンポーネントからpropsで渡した値を子コンポーネントの<style>
へ反映させる方法をまずは紹介したいと思います。
※この記事は<script setup>
構文を使用しています。
具体例
ということで本題のコードになります。
簡易的な共通デザインのボタン用コンポーネントです。
<script setup>
defineProps({
text: {
type: String,
default: ''
},
backgroundColor: {
type: String,
default: 'blue'
},
disabled: {
type: Boolean,
default: false
}
});
</script>
<template>
<button
type="button"
class="common_button"
:disabled="disabled"
>
{{ text }}
</button>
</template>
<style lang="scss" scoped>
.common_button {
min-width: 120px;
min-height: 40px;
color: white;
padding: 5px 20px;
border-radius: 30px;
background-color: v-bind(backgroundColor); // propsで設定された色を反映
&:hover:not(:disabled) {
opacity: 0.7;
cursor: pointer;
}
&:disabled {
background-color: gray;
}
}
</style>
超シンプルなので親側の記述は省略。
肝になるのがbackground-color: v-bind(backgroundColor);
の部分で、propsに設定したbackgroundColor
の値がstyleとして反映されます。
デフォルト値はblue
なのでもし何も設定していなければ青色のボタンになりますね。
このv-bind
自体はVueのディレクティブではあるのですが、<style>
でも使えるという点が個人的に盲点でした。(何か書き方違うけど)
ということで、v-bind
を使えば設定した変数は<style>
内に反映できます。
つまりcomputed
を使ったりすればさらに色々と動的にスタイルを設定できるということですね。
また、v-bind()
内でシングルクオーテーションを使えば、プロパティ参照とかも可能になります。
<script setup>
import { computed } from 'vue';
const props = defineProps({
text: {
type: String,
default: ''
},
backgroundColor: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
}
});
// ボタン背景色設定
// 設定があればそれを優先し、無ければエクスクラメーションの有無で赤or青
const buttonBgColor = computed(() => {
if (props.backgroundColor) { return props.backgroundColor; }
return ['!', '!'].some((str) => { return props.text.includes(str); }) ? 'red' : 'blue';
});
</script>
<template>
<button
type="button"
class="common_button"
:disabled="disabled"
>
{{ text }}
</button>
</template>
<style lang="scss" scoped>
.common_button {
min-width: 120px;
min-height: calc(v-bind('text.length') * 1px + 40px); // 文字列の長さによってボタンのサイズを変更
color: white;
padding: 5px 20px;
border-radius: 30px;
background-color: v-bind(buttonBgColor);
&:hover:not(:disabled) {
opacity: 0.7;
cursor: pointer;
}
&:disabled {
background-color: gray;
}
}
</style>
サンプルなので超意味のないことをしていますが、こんな設定も可能ということです。
computed()
で背景色を変えているのは分かりやすいと思いますが、calc(v-bind('text.length') * 1px + 40px);
という形でCSSのcalc()
と組み合わせて計算させるのも可能です。
使用するタイミングは限定的だと思いますが、配列で表示する項目を渡して、その配列の長さだけグリッドレイアウトのgrid-row-template
を設定して~とかも出来ます。
総じて、スタイルにも直接<script>
で設定した変数なりを参照できるのは便利だと感じました。
scssを駆使してもなかなか複雑な条件分岐を表現することは出来ないのですが、スクリプトを経由することで複雑な条件でも設定することが出来るようになります。Vueらしくスタイルとスクリプトが単一で記述されてその参照も直接的で分かりやすい形になっているのもいいですね。
そこまで頻繁に使うものでもないですが、手札の一つして持っておくと良さそうです。
その他の方法
親コンポーネントからprops経由で子コンポーネントへ値を渡す所は同じですが、そこから先の方法自体はいくつかあります。
他にもあると思いますが、思いついたのを参考程度に以下載せておきます。
-
template
内の対象の要素にて、style
属性へ参照させる
一番直接的かつ単純ならこれでしょうか。
ただ、個人的な印象としてはまあ出来るけど…うーんって感じです。Vueには<style>
というスタイルを記述すべき場所があるので、超特殊な事情でも無ければstyle
属性を使うのは避けたいですね。
-
template
内の対象の要素にて、class
へ参照させる
これもありですが、予め設定したclass
の値を親コンポーネントから渡す形になるのがネックですね。何なら設定できるの?ってなりそう。
そこはpropsのvalidator
を使うとか工夫は色々あると思いますが、それでも柔軟性には欠ける形になります。
複数のCSSプロパティをまとめて分岐して設定したいケースならこの方法が良いと思うので、用途によって考える形になりますね。
-
親コンポーネントからフォールバック属性として
class
を指定し、親コンポーネントのスタイルで記述する
要は子コンポーネント側で設定するのを諦めて、親コンポーネント側で全部指定するやり方です。
もちろん方法の一つではありますが、あんまりやってしまうとそもそも別のコンポーネントに分離したほうがいいのでは?となります。
設定の自由度は高いですが、どちらかと言えばカオスを生み出しそうなので個人的には避けたいですね。
あと、scoped
を使っているとそもそもルート要素にしか反映されないのでその点も注意です。
-
親コンポーネントのスタイルで
:deep()
セレクタを使う
これも3と同様に親コンポーネント側で全部指定するやり方です。
3と違うのは、scoped
を使いながらでも子のルート要素よりさらに深い要素へ影響を及ぼせることですね。
問題点は3と同じなので、まああんまり使いたい方法ではないですね。
最後に
親コンポーネントから子コンポーネントのスタイルを操作する方法を書いてきましたが、当然いくつか方法があるので考えて使いましょうというお話でした。
個人的には上述の通りv-bind
を使って<style>
内に突っ込む、を第1候補にしていますが要件等で変えていってよいと思います。
まだまだVueの経験が浅いので、色々と知らないといけないですね。
参考
-
SFC CSS 機能 | Vue.js
↑今回紹介した方法はこちらに記載されています。しっかり読まないと分からないですね... - ビルトインのディレクティブ | Vue.js
- クラスとスタイルのバインディング | Vue.js