echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Vue.js】子コンポーネントの埋め込み方法+各種プロパティ設定方法(2021/12/03:一部加筆修正)

経緯

最近、仕事で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;
  /* box-shadow: 0px 3px 3px rgba(0,0,0,0.1); */
  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: {
    // ここで子コンポーネント&HTMLタグ名を登録
    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内での子コンポーネントの埋め込み方法は同じです。

// main.js  
  
// 子コンポーネントの読み込み
import bodyHeader from './components/common/header'  
  
// 1. グローバルコンポーネントとして登録する方法。  
// こうすると、全Vue.jsでbodyHeaderコンポーネントが使えるようになる
Vue.component('bodyHeader', bodyHeader);  
  
// 2. 個別に設定する場合。  
// 個別に設定する場合は、Vue.component()は使用せず、
// new Vue()のcomponentsプロパティに設定する。  
// 下の場合、#fugaに対応したVue.jsでのみ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() {
    // 子コンポーネントのpage1へのリンクに「page1へ飛びます」と表示される。
    this.$refs.myBodyHeader.setter('page1へ飛びます');
  }
}
</script>  
  
<style scoped>
</style>  

(2021/12/03追記) propsを使う方法

上記では子コンポーネントのsetterを直接呼びましたが、propsを使う方法もあります。(てか、むしろこっちの方が一般的かも...)

propsの使い方ですが、子コンポーネントのscriptタグで、こんな感じで定義します。

<script>    
export default {
  // propsの定義
  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() {
    // 子コンポーネントのpage1へのリンクに「page1へ飛びます」と表示される。
    this.$refs.myBodyHeader.setter('page1へ飛びます');
  }
}
</script>  
  
<style scoped>  
.parent .bh >>> .my_item {
  /* 子コンポーネントの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 {
  /* もちろん、直書きでもOK */
  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() {
       // this.bgClassに直接クラス名を埋め込む場合
       return this.bgClass;
       
       // this.bgClassにフラグ(0, 1やred, blueなどの値)を埋め込む場合  
       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うまく表示してくれるとか)