Vue.jsのスロット機能を使ってみると意外に便利だったため、ざっくりまとめます。

スロット

Vue.jsのスロットとは、簡単にいうと「 子コンポーネントの一部を親コンポーネントから差し込む 」ことができる機能です。

Vue.js > ガイド > コンポーネント > スロットによるコンテンツ配信
https://jp.vuejs.org/v2/guide/components.html#スロットによるコンテンツ配信

コードサンプル

スロットは、Bootstrapのようにタグ構成が決まっているコードのコンポーネント化と相性が良いと感じます。

さっそく例を見てみましょう。

確認環境

  • Node.js 7.7.x
  • mpn 5.3.x
  • Vue.js 2.4.x

Panelを単一スロットのコンポーネントにする

整形前のコード

<div class="panel panel-primary">
  <div class="panel-body">
    Panel content
  </div>
</div>

コンポーネント化

次はpanel-bodyの文字を<slot>タグでマークしただけのものです。

<template>
  <div class="panel panel-primary">
    <div class="panel-body">
      <slot>
        Panel content
      </slot>
    </div>
  </div>
</template>

このように無名の<slot>を一つ用意するものは、ドキュメントでいう単一スロットにあたります。


このスロットにコンテンツを流し込むときは次のようにします。

<template>
  <panel>
    <p>新しい朝が来た希望の朝だ!</p>
  </panel>
</template>

これを表示すると次のコードに置き換えられます。

<div class="panel panel-primary">
  <div class="panel-body">
    <p>新しい朝が来た希望の朝だ!</p>
  </div>
</div>

コンポーネントの中身がそのままSlotに差し込まれるイメージです。
簡単ですね!

Panelを単一スロット+名前付きが混在したコンポーネントにする

PanelをBodyだけで使うことって、多分あまりありませんよね?

次にヘッダーをつけたPanelのスロット活用を行います。

整形前のコード

<div class="panel panel-default">
  <div class="panel-heading">
    Pannel header
  </div>
  <div class="panel-body">
    Panel content
  </div>
</div>

コンポーネント化

<template>
  <div class="panel panel-default">
    <div class="panel-heading">
      <slot name="header">
        Pannel header
      </slot>
    </div>
    <div class="panel-body">
      <slot>
        Panel contentaaaba
      </slot>
    </div>
  </div>
</template>

<slot>タグが2つになりましたが、panel-heading内の<slot>タグにはname属性で名前を与えています。
ドキュメントで言うところの名前付きスロットにあたります。


2つのスロットにそれぞれコンテンツを差し込んでみましょう。

<template>
  <panel>
    <h4 slot="header">ラジオ体操の歌</h4>
    <p>新しい朝が来た 希望の朝だ!</p>
    <p>喜びに胸を開け 大空あおげ</p>
  </panel>
</template>

これを表示すると次のコードに置き換えられます。

<div class="panel panel-default">
  <div class="panel-heading">
    <h4>ラジオ体操の歌</h4>
  </div>
  <div class="panel-body">
    <p>新しい朝が来た 希望の朝だ!</p>
    <p>喜びに胸を開け 大空あおげ</p>
  </div>
</div>

ブラウザでチェックすると、、、問題なく表示されています。

名前付きスロットにコンテンツを差し込むには、ラップする要素にslot属性で差し込む先を指定します。
今回のように名前あり/なしが混在している場合、 slot属性の指定のないものはすべてデフォルトスロットに差し込まれる という挙動です。

理屈はわかりますが、なんとなく気持ちが悪いですね。
「複数のスロットを用意する場合は全て名前付きにする」というようなルールにしてしまった方がわかりやすいかもしれません。


ちなみに1コンポーネントに名前なしスロットが複数ある場合は次のように怒られます。

[Vue warn]: Duplicate presence of slot "default" found in the same render tree - this will likely cause render errors.

フォームでスロットを活用する

整形前のコード

次はよく見るBootstrapで組み立てたフォームです。

このフォームは素のBootstrapにv-modelを加えて実装しております。

<form class="form-horizontal">
  <fieldset>
    <legend>書籍登録/変更</legend>

    <div class="form-group">
      <label for="inputTitle" class="col-md-3 control-label">タイトル</label>
      <div class="col-md-9">
        <input v-model="title" type="text" class="form-control" id="inputTitle" placeholder="書籍タイトル">
      </div>
    </div>

    <div class="form-group">
      <label for="inputTitleSub" class="col-md-3 control-label">サブタイトル</label>
      <div class="col-md-9">
        <input v-model="title_sub" type="text" class="form-control" id="inputTitleSub" placeholder="書籍サブタイトル">
      </div>
    </div>

  </fieldset>
</form>

ちょっと無理やりですが、ここも繰り返し部分をコンポーネントにしてみましょう。

コンポーネント化

<template>
  <div class="form-group">
    <label :for="target" class="col-md-3 control-label">{{ label }}</label>
    <div class="col-md-9">
      <slot></slot>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    label: String,
    target: String
  },
}
</script>

先のコードを、この子コンポーネントを利用した形に書き換えたものが次です。

<form class="form-horizontal">
  <fieldset>
    <legend>書籍登録/変更</legend>

    <input-component label="タイトル" target="inputTitle">
      <input v-model="title" type="text" class="form-control" id="inputTitle" placeholder="書籍タイトル">
    </input-component>

    <input-component label="サブタイトル" target="inputTitleSub">
      <input v-model="title_sub" type="text" class="form-control" id="inputTitleSub" placeholder="書籍サブタイトル">
    </input-component>

  </fieldset>
</form>

かなり見通しが良くなりましたね!
こういうすっきりシンプルな実装、私は大好きです。


この実装で混乱するところは v-modelのスコープは親と子のどちらにあるか です。
答えは親です。

ドキュメントにも記載があ流通り、Vue.jsでは「 親テンプレート内の全てのものは親のスコープ 」というルールがありますので覚えておくと良い感じです。

親テンプレート内の全てのものは親のスコープでコンパイルされ、子テンプレート内の全てものは子のスコープでコンパイルされる
https://jp.vuejs.org/v2/guide/components.html#コンパイルスコープ

おわりに

スロットをうまく活用し、再利用生の高いコンポーネントを用意しておくと開発スピードのアップが期待できますね。
どこをスロット化するか をしっかり設計するのがポイントだと感じました。

この記事はtomita@atuwebがお届けしました。



Learning Vue.js 2

スポンサーリンク
ad_336
ad_336
  • このエントリーをはてなブックマークに追加
  • Evernoteに保存Evernoteに保存
コメントの入力は終了しました。