経緯
最近、仕事でVue.jsを触っており(AWSによるサーバーレスSPA)、何とか一週間で他のメンバが開発をできる土台(フレームワーク的なもの)をVue.jsで作成する...程度はできるようになりました。
その中で、コンポーネント(*.vue)の扱いでよく使いそうな、子コンポーネントの扱いについて便利そうな事項をまとめました。
【2021/12/03追記】:最初(2019/7/27)に公開した内容を元に、加筆修正を行いました
参考ページ
単に親コンポーネント内に子コンポーネントを埋め込む場合は、下記でOKです。
<template>
<div id="body_header">
<router-link to="/">page1</router-link>
<router-link to="/page2">page2</router-link>
</div>
</template>
<script>
export default {
name:"body_header",
components: {},
data: function () {
return {}
},
}
</script>
<style>
#body_header {
height: 40px;
background: white;
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px #000000 solid;
}
#body_header a {
text-decoration: none;
color: #2c3e50;
margin: 0 10px;
padding: 3px 10px;
background: #5ccebf;
}
</style>
<template>
<div id="app">
<!-- ここで埋め込む -->
<bodyHeader></bodyHeader>
<router-view></router-view>
</div>
</template>
<script>
import bodyHeader from './components/common/header'
export default {
name: 'App',
components: {
bodyHeader: bodyHeader,
},
data: function () {
return {}
}
}
</script>
<style>
body {
margin: 0;
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
【2021/12/03追記】エントリーポイントに定義する方法
上記は各Vue.jsファイルに直接埋め込みましたが、複数のVue.jsファイルでまたがって使用する場合は、エントリポイント(main.jsなど)に定義することもできます。
※親コンポーネントのHTML内での子コンポーネントの埋め込み方法は同じです。
import bodyHeader from './components/common/header'
Vue.component('bodyHeader', bodyHeader);
new Vue({
el: '#hoge',
})
new Vue({
el: '#fuga',
components: {
'bodyHeader': bodyHeader,
}
})
子コンポーネントの使い方でよくあるのが、下記の例だと思います。
その場合、子コンポーネントの変数やスタイルシートなどを変更する必要があると思いますが、そのやり方です。
子コンポーネントの変数を親コンポーネントから変更する場合、下記でOKです。
<template>
<div id="body_header">
<router-link to="/page1">{{ $myLink }}</router-link>
</div>
</template>
<script>
export default {
name:"body_header",
components: {},
data: function () {
return {
myLink : ''
}
},
methods: {
setter(linkValue) {
this.myLink = linkValue;
}
}
}
</script>
<template>
<div id="app">
<bodyHeader ref="myBodyHeader"></bodyHeader>
</div>
</template>
<script>
import bodyHeader from './components/common/header'
export default {
mounted: function() {
this.$refs.myBodyHeader.setter('page1へ飛びます');
}
}
</script>
<style scoped>
</style>
(2021/12/03追記) propsを使う方法
上記では子コンポーネントのsetterを直接呼びましたが、propsを使う方法もあります。(てか、むしろこっちの方が一般的かも...)
propsの使い方ですが、子コンポーネントのscriptタグで、こんな感じで定義します。
<script>
export default {
props: {
isHoge: {
type: Boolean,
default: false,
required: true,
validator: function (value) {
return typeof value === 'boolean'
}
}
},
data() {
return {
}
}
}
</script>
上のコードで言えば、「isHoge」はpropsの変数名です。
また、各プロパティの意味は以下の通りです。(すべて任意項目です)
key |
意味 |
備考 |
type |
propsの型を表す型クラス(のコンストラクタ) |
型クラスであることに注意。(プリミティブ型でも「string」「boolean」などはダメ) |
default |
値が指定されなかった場合の初期値 |
|
required |
値の指定が必須かどうか |
|
validator |
指定された値をチェックするバリデーション関数 |
|
で、親コンポーネントでは、以下のように子コンポーネントのタグ内でpropsの値を指定します。
<template>
<div id="app">
<bodyHeader isHoge="true"></bodyHeader>
</div>
</template>
参考:https://jp.vuejs.org/v2/api/#props
ただし、propsは初回レンダリング時は良いんですが、propsを変更してうんぬん...みたいなことをやろうとすると、途端に厄介になります。(ググってみると色々出てきますし、自分もハマりました)
このあたりは、別途独立してブログに出来ればなあ、と思っています。
参考: https://blog.hatena.ne.jp/Makky12/makky12.hatenablog.com/edit?entry=26006613378542201
子コンポーネントのスタイルを変更する場合は、ちょっと厄介ですが、下記の方法でOKです。
※なお、親コンポーネントのstyleタグにscopeを付けない方法もありますが、本来の使い方ではないですし、Vue.jsのスタイルルールA(必須)に反するので、お勧めしません。
- 子クラスのスタイルを採用したいHTMLタグに、何かクラスを定義する。
- 親クラスで、子クラス埋め込みタグの親要素として、何かクラス付きのタグを埋め込む
- 親クラスのstyle内で、「(子クラス埋め込みタグの親要素クラス) (子クラス埋め込みタグクラス) >>> (子クラスのスタイルを採用したい要素のクラス)」という定義を用意する。
<template>
<div id="body_header">
<div class="my_item">{{ $myItem }}</div>
</div>
</template>
<template>
<div id="app">
<div class="parent">
<bodyHeader class="bh"></bodyHeader>
</div>
</div>
</template>
<script>
import bodyHeader from './components/common/header'
export default {
mounted: function() {
this.$refs.myBodyHeader.setter('page1へ飛びます');
}
}
</script>
<style scoped>
.parent .bh >>> .my_item {
background-color: #0000FF;
}
</style>
【2021/12/03追記】
もちろん、子コンポーネントに直接cssを書いてもOKです。
<template>
<div id="body_header">
<div class="my_item">{{ $myItem }}</div>
</div>
</template>
<style scoped>
.my_item {
background-color: #0000FF;
}
</style>
(2021/12/03追記) 子コンポーネントのcssを動的に変えたい場合
上記とは少し違いますが、場合によっては「親の状態に応じて、子のスタイルを動的に変えたい」というケースもあると思います。
その場合、方法としては、下記のようなものがあります。
- 親から受け取る変数を「v-if」条件にする
- class名を変更する
- cssクラスのプロパティの値のみ変更する
親から受け取る変数を「v-if」条件にする
これは何となくわかりやすと思います。
ただ、HTML部分の分岐が増えてしまうのがちょっと面倒ですね...
<template>
<div id="body_header">
<div if="bgColor==='red'" class="bg_color_red">{{ $myItem }}</div>
<div v-else class="bg_color_blue">{{ $myItem }}</div>
</div>
</template>
<script>
export default {
data() {
return {
bgColor: ''
}
},
methods: {
setBgColor(color) {
this.bgColor =color;
}
}
}
</script>
class名を変更する
親から取得した変数の値に応じて、class名を変更する方法です。(classはv-bindしておく)
この場合、computed関数「getBgClass」に定義したように、
- 親から受け取ったクラス名をそのまま返す
- 親からは状態(state)を受け取り、それに応じたクラス名を返す
などの方法があります。
<template>
<div id="body_header">
<div :class="getBgClass">abc</div>
</div>
</template>
<script>
export default {
data(): {
return {
bgClass: ''
}
},
computed: {
getBgClass() {
return this.bgClass;
if (this.bgClass==='red') {
return 'bg_color_red';
} else if (this.bgClass==='blue') {
return 'bg_color_blue';
} else {
}
},
},
methods: {
setBgClass(value) {
this.bgClass = value;
}
}
}
</script>
cssクラスのプロパティの値のみ変更する
<template>タグ内に<component is="style">タグを記載することで、<template>タグ内にcssを直接記述することが可能になります。
またVue.js 2.xでは、styleタグ内のcssの値は、data()変数やcomputed関数などの値をバインディングすることはできない(はず)ですが、上記のcssならば、それを行うことが可能になりますので、タグ内のclass名を直接変更せずとも、スタイルを動的に変更することができます。
この方法だと、上2つの方法と違い、
- 条件が増えても、(<template>内の)HTMLタグが増えない
- class名を直接変更する必要がない(=class定義を分岐数分増やさなくていい)
というメリットがあります。
※Vue.js 3.2から、styleタグ内のcssの値にdata()変数の値をバインディングできるみたいです
<template>
<div>
<component is="style">
.bg_class {
background-color: {{ getBgColor }}
};
</component>
<div id="body_header">
<div class="bg_class">abc</div>
</div>
</div>
</template>
<script>
export default {
data(): {
return {
bgColor: ''
}
},
computed: {
getBgColor() {
if (this.bgColor==='red') {
return '#FF0000';
} else if (this.bgColor==='blue') {
return '#0000FF';
} else {
}
},
},
methods: {
setBgColor(color) {
this.bgColor = color;
}
}
}
</script>
ちなみに
もちろん、1つの親コンポーネント内で同じ子コンポーネントを使いまわすことも可能です。
<template>
<div id="body_header">
<div class="my_item">{{ $myItem }}</div>
</div>
</template>
<script>
export default {
name:"body_header",
components: {},
data: function () {
return {
myItem : ''
}
},
methods: {
setter(itemValue) {
this.myItem = itemValue;
}
}
}
</script>
<style scoped>
</style>
<template>
<div id="app">
<div class="parent">
<bodyHeader ref="myBodyHeader" class="bh1"></bodyHeader>
</div>
<div class="parent">
<bodyHeader2 ref="myBodyHeader2" class="bh2"></bodyHeader>
</div>
</div>
</template>
<script>
import bodyHeader from './components/common/header'
import bodyHeader2 from './components/common/header'
export default {
components: {
bodyHeader : bodyHeader ,
bodyHeader2: bodyHeader2
},
mounted: function() {
this.$refs.myBodyHeader.setter('bodyHeader1');
this.$refs.myBodyHeader2.setter('bodyHeader2')
}
}
</script>
<style scoped>
.parent .bh1 >>> .my_item {
background-color: #0000FF;
}
.parent .bh2 >>> .my_item {
background-color: #FF0000;
}
</style>
ちょっと急ぎ足&文字数が多くなりましたが、今回はここまで。
...てかはてなブログ、Vue.jsに対応してくれないかなあ。(HTML/CSS/javascriptうまく表示してくれるとか)