はじめに
vuejsはとっつきやすく完成度の高いJSフレームワークだと思っていますし、日本語のドキュメントも充実しています、実際に下記の機能も全て公式ドキュメントに記載されている内容です。
しかしながら、実際にやりたいことと機能名の間に乖離があったりして、いざ使用する時に意外に実装方法などを見つけにくい場合も当然ながら存在するため、自分用に Vuejs のちょっと便利なコンポーネントの機能をメモっておきます。
お品書き
- 共通した親要素を使いまわしたい(スロット配信)
- パラメーターによって使用するコンポーネントを変えたい(動的コンポーネント)
- 複数のコンポーネントで、同じ処理を使いまわしたい(ミックスイン)
- 出力するタグを動的に指定したい
共通した親要素を使いまわしたい
いわゆるラッパーコンポーネント、例えば10個のコンポーネントを作る必要があるとして、その全てが共通したヘッダやフッタを持っている場合というのは、そう珍しい状況ではないはず。
具体的にはこういう状況
<div>
<header>同じ内容</header>
<section>Aです!</section>
<footer>同じ内容</footer>
</div>
<div>
<header>同じ内容</header>
<section>Bです!Aじゃないです!</section>
<footer>同じ内容</footer>
</div>
つまり親要素は共通なんだけど、子要素はコンポーネントごとに違うという場合に、全部のコンポーネントにイチイチ同じ要素をコピペしてくるのは面倒だし、ヘッダー全部差し替えたいとかいう時に面倒。
そんな時は
<div>
<header>同じ内容</header>
<slot></slot><!--ここに子要素が入ってくる-->
<footer>同じ内容</footer>
</div>
<Wrapper>
<section>Aです!</section>
</Wrapper>
<Wrapper>
<section>Bです!Aじゃないです!</section>
</Wrapper>
という感じで「スロットによるコンテンツ配信」を使えばOK!
パラメーターによって使用するコンポーネントを変えたい
さて、前述のように10個のコンポーネントを作ったとする、しかし実際にはヘッダーは全部同じじゃなくて、特定の場合だけヘッダーにボタンがついたりリンクがついたりする、というのもよくある話。
「ボタンを一つ追加する」くらいであれば、コンポーネント内で v-if とか使ってなんとかすればいいと思いますが、それでは到底収まらないとか、面倒かつ可読性が悪化するなどの理由で、表示するヘッダ用のコンポーネントをあらかじめ複数用意しておいて、コンポーネントの親から渡った props で表示するヘッダを判別する、というような実装にしたいとしましょう、その場合は
<div>
<component :is="currentHeader"><!--ここで読み込むコンポーネントが動的に変わる-->
<slot></slot>
<footer>同じ内容</footer>
</div>
import IndexHeader from './IndexHeader.vue'
import SearchHeader from './SearchHeader.vue'
import ArchiveHeader from './ArchiveHeader.vue'
export default {
props: ['currentHeader'],
components: {
Index: IndexHeader,
Search: SearchHeader,
Archive: ArchiveHeader
}
}
という感じで、props の currentHeader で表示するコンポーネントを分岐できます。
複数のコンポーネントで、同じ処理を使いまわしたい
さて、やはり10個のコンポーネントがあるとして、SPAでページ遷移する際にスクロール位置が保持されてしまい、そのままだと変な位置からページがはじまってしまうため、ページ遷移直後は常に画面の最上部までスクロールしたいとします。
created () {
window.scroll(0,0)
}
もちろんこんな感じのコードを全てのコンポーネントに書いておけばそれは実現できますが、それだと特定のコンポーネントに書くのを忘れていたり、後に変更したいという場合にやはり面倒です、そういう場合
export default {
created () {
window.scroll(0,0)
}
}
import Init from './Init.vue'
export default {
mixins: [Init],/* ここでミックスインを追加 */
components: {},
data () {
return {}
}
}
という感じでミックスインを追加すると、複数コンポーネントで同じ処理を使いまわせます。
出力するタグを動的に指定したい
例えばサッカーのページを作っているとして、選手の横に「イエローカード(or レッドカード)を何枚もらっているのか?」を表示したいとします、どのカードをもらっているかは「card_type」という変数で判別してカードによって適切な className を付与するという仕組みです。
<template>
<p v-if="card_type==='yellow'" class="yellow">{{ text }}</p>
<p v-if="card_type==='yellow2'" class="yellow2">{{ text }}</p>
<p v-if="card_type==='red'" class="red">{{ text }}</p>
</template>
雑に書くと上記のような感じ。
実際はこれを色んなページで使いたいとします、すると「card_type」の処理は共通ですが、ページによって表示したいタグやクラス名が違うという場合があります。
つまり、あるページでは下記のような出力をしたいのですが、
<template>
<p class="warningYellow">hogehoge</p>
</template>
あるページでは下記のようなDOMにしたい、という場合です。
<template>
<span class="cardYellow">hogehoge</span>
</template>
この場合、カードの『処理を全て共通のコンポーネントに切り出してしまい、その中でタグやクラス名を動的に変更する』ということが出来ます、説明だけだと分かりにくいのですが具体的には
<template>
<component :is="tagName" :class="cardsClass">{{ text }}</component>
</template>
<script>
export default {
props: ['tagName', 'className', 'type', 'text'],
computed: {
cardsClass() {
switch(type) {
case 'yellow':
return `${this.className}Yellow`
case 'yellow2':
return `${this.className}Yellow2`
case 'red':
return `${this.className}Red`
}
}
}
}
</script>
こういう感じのコンポーネントを作成します、ポイントは<component :is>は動的なタグ出力にも使えるという事実です。
具体的には下記のようなプロパティを与えると…
<template>
<card-component tagName="span" className="card" :type="yellow" :text="ここです" />
</template>
下記のようなタグになって出力されます。
<span class="cardYellow">ここです</span>
同様に下記のようなプロパティを与えると…
<template>
<card-component tagName="p" className="warning" :type="yellow" :text="ここです" />
</template>
下記のようなタグになって出力されます。
<p class="warningYellow">ここです</p>