Vue.js

Vue.js スロットとは

こんにちは!ともです(@_tomo_engineer)!

今回は『スロット』について見ていきます。アルバイト先でVueファイルにslotってよく出てくるので正体は何者か気になってました。

参考にしたものは以下です。

基礎から学ぶVue.js(書籍)

Vue.js:日本語ドキュメント

スロットとは

『スロット』とは親側のコンポーネントから子のコンポーネントに『テンプレートの一部を差し込む機能』の事です。テンプレートをカスタマイズしたいときに利用します。

以前、コンポーネントの定義の方法について書いました。

その時、親側のコンポーネントは次のように書きました。

<div id="app">
    <my-component>/* 何も存在しないここはスロットの領域 */</my-component>
</div>

実はタグの中は親から子にデータを差し込むスロットの領域になっています。

親から子へのデータ渡しは次の方法が思い浮かぶのではないでしょうか?

親から子

親から子にデータを渡す方法に、属性とpropsの方法がありました。

# 親コンポーネント
<div id="app">
     <child-comp val="parentData"></child-comp>
</div>

# 子コンポーネント
var childComp = {
    template: '<div class="child-comp">{{val}}</div>',
    props: ['val']
}

こちらのように属性として、子コンポーネントに親データを渡しpropsで受け取るというものです。

親から子にデータを差し込むスロットと似ています。

スロットで書くと次のようになります。

スロットの書き方

先ほどの親から子へのデータ受け渡しをスロットで行うと次のようになります。

# 親のコンポーネント
<child-slot-comp>スロットで親のデータ</child-slot-comp>
# 子コンポーネント
var childSlotComp = {
    template: '<div class="child-slot-comp"><slot></slot></div>',
}

 

こちらのように書く事ができました。<slot></slot>で囲まれた所に、親から子へ渡すデータ(スロットコンテンツ)が差し込まれます。

スロットコンテンツがなかった場合のデフォルト値も設定できます。

// スロットで渡す子コンポーネント
var childSlotComp = {
    template: '<div class="child-slot-comp"><slot>何もない場合</slot></div>', 
}

スロットコンテンツが存在しない場合は、<slot></slot>内のデータが表示されます。

スコープ

親と子が同じ名前のデータを持っており、スロットコンテンツで描画させた場合、どちらのデータを描画するのでしょうか。

# HTML
<div id="app">
        <child-slot-comp>{{message}}</child-slot-comp>
</div>
# 子コンポーネント
var childSlotComp = {
    template: '<div><slot></slot></div>',
    data: function(){
        return{
            message: '子のmessageだよ。'
        }
    }
}
# 親のビューインスタンス
new Vue({
    el: '#app',
    components: {
        'child-slot-comp': childSlotComp
    },
    data: {
        message: '親のmessageだよ。'
    }
})

正解は親のmessageが表示されるでした。

スロットコンテンツ定義側のスコープ、つまり親側のスコープとなります。

名前付きのスロット

<slot>により親から子に、データを差し込める事がわかりました。

では、差し込みたい場所に差し込むにはどうすれば良いのでしょうか。

その時には名前付きのスロットが利用できます。

# 親コンポーネント
<div id="app">
        <child-slot-comp>
            <header slot="header">
                ヘッダーです。
            </header>
            デフォルトです。
        </child-slot-comp>
</div>
# 子コンポーネント
var childSlotComp = {
    template: `<div class="child-comp">
                    <slot name="header"></slot>
                    <slot></slot>
                </div>`,
}

名前付きのスロットを利用することで、好きな場所に差し込む事ができます。

  • 親テンプレートのslot属性
  • 子テンプレートのname属性

が対応する形になっています。

propsを利用した親から子へのデータの受け渡しによっても実現はできますが、こちらの方がHTML的に書けるので直感的に理解しやすいです。

スロットコンテンツの定義にタグが不要の場合

<template>タグを利用し、スロットコンテンツをグループ化する事ができます。

<div id="app">
        <child-slot-comp>
            <template slot="text1">テキスト1</template>
            <template slot="text2">テキスト2</template>
        </child-slot-comp>
</div>

このようにタグを囲む必要のないものにslotを付けたい場合は<template>タグを利用するとこができます。

スロットコンテンツ内で子のデータにアクセスする方法

スロットコンテンツ内は親のスコープが効いていることが分かりました。

では、『スロットコンテンツないで子のデータを利用する場合』はどうするのでしょうか。

# 子コンポーネント
var childSlotComp = {
    template: `<div class="child-comp">
                    <slot v-bind:message="message"></slot>
                </div>`,
    data: function(){
        return{
            message: '子のmessageだよ。'
        }
    }
}
# 親コンポーネント
<div id="app">
        <child-slot-comp>
            <template slot-scope="props">{{props.message}}</template>
        </child-slot-comp>
</div>

これにより親のスロットコンテンツで子のデータを利用する事ができます。

コンポーネント間の通信で、親から子へデータを渡す場合『親が属性で渡して、子がpropsで受け取る』というものがありました。

今回はそれと逆の関係にあり、『属性で渡して、親がpropsで受け取る』っています。親から子の逆である事を考えると素直な受け渡しに思えます。

まとめ

今回は『スロット』について書きました。

まとめは次のようになっています。

まとめ
  • 『slot』は親が子テンプレートに差し込むためのタグである
  • 親から子へのpropsによるデータ受け渡しよりも分かりやすい
  • 名前付きのスロットで、差し込みたい所に差し込める
  • スロットコンテンツは親のスコープである
  • スロットコンテンツで子のデータが利用したい場合、『slot-scope』が利用できる

slotによる新たなデータの受け渡しを習得する事ができました。

単純なデータではpropsを利用してデータを渡せば良いですが、テンプレートをカスタマイズする場合はslotが有効に感じました。

しっかり使い分けて行きたいです。