ryoのぼやき

技術についての学習内容のまとめです。

【Vue.js】slotについて(スコープ付きslot、propとの使い分けetc)

この記事の内容

Vue.jsにはslot(スロット)という仕組みがあり、 その中にさらに、スコープ付きスロットという仕組みがあります。 これが、理解しづらく感じる人が多いと思うので、 僕なりの理解を説明します。

はじめに slotとは

  • slotとは、Vue.jsのテンプレート(<template>)で使えるタグの1つです。
  • ざっくりとした言い方をすると親コンポーネントから、子コンポーネントに描画内容を直接渡すことができるタグです。
  • コンポーネントから渡した描画内容を、子コンポーネントに直接表示することができます。
  • propsは基本的に値を渡すのに対して、slotでは描画内容を渡します。

    用途

    コンポーネントでは、描画内容を決定できないときに使います。
    コンポーネントとして、
    「この部分に、こういう役割のものを表示したい。
    けれど、どのように表示するかは、このコンポーネントを使う側で決めてほしい」

    という時です。

    例えば、スマートデバイスのヘッダーを考えてみてください。

右上にボタンを置くことは、モバイルアプリでもよくあると思います。
Twitterだと、このような感じですね。

f:id:greko_prg:20200211192714p:plain

f:id:greko_prg:20200211192711p:plain


Twitterの場合もそうですが、画面によってこの右上のボタンって見た目や用途が変わりますよね

例として、この右上のボタンを1つのコンポーネントとして実装するとこうなるかと思います。

<template>
  <div v-if="currentScreen === 'home' ">
    <star-button/>
  </div>
  <div v-else-if="currentScreen === 'search' ">
    <setting-btn/>
  </div>
</template>


これだと、右上のボタンで表示する内容が増えるたびに、
分岐が増えてしまいます。

なので、この右上の領域に何をどのように表示するかは、
使う側の親コンポーネントに全て任せたいという考えになります。

それを実現するのがslotというわけです。
右上の領域をslotとして解放しておくことで、
コンポーネントを使う側に描画内容を自由に決めてもらうことができます。

slotの使い方

以下にVue.js公式ドキュメントの例を載せます。

コンポーネントで子コンポーネントを使用(値を渡す側)

<navigation-link url="/profile">
  Your Profile
</navigation-link>


コンポーネントの定義(値を受け取る側)

<!-- navigationLink.vueのテンプレート -->
<a
  v-bind:href="url"
  class="nav-link"
>
  <slot></slot>
</a>


最終的に下のように描画されます。

<a
  v-bind:href="url"
  class="nav-link"
>
  Your Profile
</a>


複数のスロットを使いたいときは、名前付きスロットを使用します。
コンポーネント("my-component"とします)

<div>
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot name="main"></slot>
  </main>
</div>


コンポーネント

<my-component>
  <template v-slot:header>
    <h1>Here is Header part</h1>
  </template>
  <template v-slot:main>
    <p>Here is Main part</p>
  </template>
</my-component>


コンポーネントタグのname属性で指定した箇所に対し、
コンポーネント<template>タグのv-slotで対応するコンテンツが入ります。

slotを使うメリット

  • slotを使うことで描画内容を親コンポーネントで決定することができます。)

→上でも触れましたが、例えばpropsを使う場合は、描画内容は子コンポーネントで決めることなります。
そして、propsによって、描画内容を切り替える処理を書かなくてはいけず、
描画内容が増えるたびに、子コンポーネントが肥大してしまいます。
そもそも汎用的なコンポーネントは、ビジネスロジックを持つべきではありません。
汎用的ではなくなってしまいますので。

どんなコンポーネントが「汎用的コンポーネント」かに関しては、
アトミックデザインでいう、Organisms(有機体)以下のコンポーネント{Atoms(原子),Molecules(分子),Organisms(有機体)}は
「汎用的コンポーネント」に該当すると言っていいと思います。(個人的見解だし、ケースバイケースです)
[参考]

Atomic DesignとCSS設計 - Atomic Designとは何か | CodeGrid



スコープ付きslotについて

しかし、子コンポーネントにアクセスがしたい状況というのは往々にしてあるので、子コンポーネントが許容したデータに限り、親コンポーネントからアクセスすることが可能です。このアクセス可能なデータはスロットプロパティと呼ばれます。
分かりづらい部分なので、ここでも例をもとに見ていきたいと思います。

まず、親コンポーネント側です。

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>


* v-slotとして指定したslotPropsを通して、子コンポーネントにアクセスすることができます。
コンポーネント側も見てみます。

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>


* 親コンポーネントにアクセスを許容するデータをv-bind:user:"user"の部分で指定しています。 * このバインドしたuserというデータ(スロットプロパティ)に対して、親コンポーネントではslotProps.userとしてアクセスできるわけです。
なので、上記の例は、次のように展開されます。

<current-user>
  <span>
    {{ user.firstName }}
  </span>
</current-user>

Vuetifyにおけるslot

Vue.jsのコンポーネントフレームワークであるVuetifyをよく使うのですが、
Vuetifyの公式ドキュメントの各コンポーネントページには、SLOTSという項目があります。

f:id:greko_prg:20191130193822p:plain
画像は、v-text-fieldコンポーネントの例

以前まで、これが何を意味しているのかよく分かっていなかったのですが、
つまり、このコンポーネントを使う場合に、親コンポーネントからアクセスが可能なデータがここに示されているという訳でした。

追記

別の例を交えて説明した記事をこちらに書いたので、
まだよく分からない方は見てみてください。
【Vue.js】スコープ付きスロットが理解できるように例を交えて考える - Qiita

まとめ

Vue.jsは直感的に理解しやすいものが多いのですが、slotはそれらと比べると直感的とは言えないかもしれません。
そういった理由から、Slotを理解することが、Vue中級者への一歩と言われているようです。
Atomicデザインに基づいたきれいなコンポーネントを設計する上では、slotの理解が必須だということが分かったので、見て見ぬ振りせずきちんと向き合っていきたいと思いました。

[参考]

スロット — Vue.js

Vue.js: slots vs. props - Nico Meyer - Medium