2018.05.05

Vue.jsコンポーネント入門 (6) slotによるコンテンツの差し込み


本記事は Vue.js コンポーネント入門の第6回です。

slot という機能を紹介します。

前提

PC ブラウザ Node npm Vue
macOS 10.12 Firefox Quantum 9.3.0 5.5.1 2.5

コンテンツの差し込み

差し込みと言っているのは、マニュアルでは「スロットによるコンテンツ配信」という言葉を使っていますが、つまりタグの間にテキストノードもしくは他の HTML 要素、Vue コンポーネントを挟み込むことです。

既存の HTML 要素で見ると分かりやすいでしょう。

HTML
<!-- テキストノードの差し込み -->
<a href="https://qiita.com">Qiitaのページ<a>

<!-- 他要素の差し込み -->
<dl>
  <dt>HTML</dt>
  <dd>HTMLは、ハイパーテキストを記述するためのマークアップ言語の1つである。</dd>
</dl>

最初の例では <a> 要素にテキストノードを差し込んでいます。 ふたつめの例では <dl> 要素に <dt> および <dd> 要素を差し込んでいます。

Vue コンポーネントでも同じことができるというわけです。

単一コンテンツの差し込み

第3回で扱った AnchorLink コンポーネントを見返してください。

HTML
<anchor-link href="https://qiita.com" text="Qiitaのページ" :new-tab="true"></anchor-link>
AnchorLink.vue
<template>
  <span class="anchor" @click="onClickLink">{{ text }}</span>
</template>

第3回めの時点では props しか紹介していませんでしたので、表示する文字列を props の text として渡しています。しかし、以下の記述の方が自然なのではないでしょうか。

HTML
<anchor-link href="https://qiita.com" :new-tab="true">Qiitaのページ</anchor-link>

上記の記述は可能です。そしてコンポーネント側で差し込まれたコンテンツを受け取るのが <slot> です。

AnchorLink.vue
<template>
  <span class="anchor" @click="onClickLink">
    <slot></slot>
  </span>
</template>

<slot></slot> の部分が差し込まれたコンテンツに置きかわります

コードの全体を貼っておきますので確認してみてください。

複数コンテンツの差し込み

コンテンツは複数差し込むこともできます。<dl> の例のような感じです。

複数差し込む場合は、差し込むコンテンツに slot 属性を持たせます。

<my-article>
  <span slot="title">スロットによるコンテンツ配信</span>
  <div slot="content">
    <p>HTML 要素と同様に、コンポーネントにコンテンツを渡すことが...</p>
  </div>
</my-article>

コンポーネント側では、<slot>name 属性で差し込むべき要素を特定します。

<template>
  <article>
    <h1>
      <slot name="title"></slot>
    </h1>
    <slot name="content"></slot>
  </article>
</template>

サンプルコンポーネント

サンプルとしてカードコンポーネントを作ってみましょう。

こちらが完成形です。

Getting started

# プロジェクトディレクトリで以下のコマンドを実行してください。
$ git clone git@github.com:MasahiroHarada/vue-components-starter-kit.git chapter-6
$ cd chapter-6
$ git checkout scss
$ npm install
$ npm install --save-dev spectre.css

コンポーネント

コンポーネントから作成していきます。

前回に引き続き今回も Spectre.css を使用します。

src/components/Card.vue
<template>
  <div class="card">
    <div class="card-image">
      <slot name="image"></slot>
    </div>
    <div class="card-header">
      <div class="card-title h5">
        <slot name="title"></slot>
      </div>
      <div class="card-subtitle text-gray">
        <slot name="subtitle"></slot>
      </div>
    </div>
    <div class="card-body">
      <slot name="body"></slot>
    </div>
    <div class="card-footer">
      <slot name="button"></slot>
    </div>
  </div>
</template>

画像、タイトル、サブタイトル(灰色の文字)、文章、ボタンを差し込めるように作っています。

スタイルシート

続いて SCSS ファイルです。

src/app.scss
$primary-color: #0052cc;

@import "../node_modules/spectre.css/src/spectre.scss";

#app {
  padding: 1rem;
}

.card-body {
  p {
    margin: 0;
  }
}

.card-image {
  img {
    width: 100%;
  }
}

エントリポイント

コンポーネントを登録します。

onClick メソッドが Card コンポーネント側ではなく Card コンポーネントを使う側(今回の場合はルートコンポーネント)に定義されている点に注意してください。

src/index.js
import Vue from "vue"

// Style
import "./app.scss"

// Components
import Card from "./components/Card.vue"

const app = new Vue({
  el: "#app",
  components: {
    Card
  },
  methods: {
    onClick () {
      alert("Clicked !")
    }
  }
})

HTML

最後に HTML ファイルです。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Vue Component Tutorial</title>
  <link rel="stylesheet" href="./dist/app.css">
</head>
<body>
  <div id="app">
    <div class="container">
      <div class="columns">
        <div class="column col-6 col-md-8 col-mx-auto">
          <card>
            <img slot="image" src="https://source.unsplash.com/g1Kr4Ozfoac/1600x900">
            <span slot="title">Microsoft</span>
            <span slot="subtitle">Software and hardware</span>
            <p slot="body">Empower every person and every organization on the planet to achieve more.</p>
            <button slot="button" class="btn btn-primary" @click="onClick">Click me</button>
          </card>
        </div>
      </div>
    </div>
  </div>
  <script src="./dist/main.js"></script>
</body>
</html>

先ほども少し触れましたが、onClick メソッドはカードコンポーネントを使う側に定義されていなければいけません。差し込むからと言って差し込む先のコンポーネントに定義するわけではないのですね。あくまでその変数なり関数が見えている場所に定義されていなければならないというルールには気をつけてください。

ちょっと余談ですが、このようなコンポーネントの何が嬉しいかというと、カードを表現するためのマークアップ上のルールを隠ぺいできることです。

今回は Spectre.css を使いましたので、カードを表現するためにはまず全体が card クラスで覆われていないといけませんし、画像は card-image クラスで囲まれていなければいけません。

このようなルールは他の CSS フレームワークでも、フレームワークを使っていなくても発生します。このルールをコンポーネントに閉じ込めてしまうことで、カード表現を使うたびにいちいちルールを意識する必要はなくなりますし、ルールそのものを変更したい(フレームワークから自前のスタイルに変更など)場合も、うまくいけば変更箇所はコンポーネントだけで済むかもしれません。


以上、本記事では Vue.js コンポーネント入門の第6回として、slotによるコンテンツの差し込み機能を紹介しました。

関連記事

Vue.js コンポーネント入門(全7回)

Vue.js 入門(全7回)