【Vue.js】slotについて(スコープ付きslot、propとの使い分けetc)
この記事の内容
Vue.jsにはslot(スロット)という仕組みがあり、 その中にさらに、スコープ付きスロットという仕組みがあります。 これが、理解しづらく感じる人が多いと思うので、 僕なりの理解を説明します。
はじめに slotとは
- slotとは、Vue.jsのテンプレート(
<template>
)で使えるタグの1つです。 - ざっくりとした言い方をすると親コンポーネントから、子コンポーネントに描画内容を直接渡すことができるタグです。
- 親コンポーネントから渡した描画内容を、子コンポーネントに直接表示することができます。
- propsは基本的に値を渡すのに対して、slotでは描画内容を渡します。
用途
子コンポーネントでは、描画内容を決定できないときに使います。
コンポーネントとして、
「この部分に、こういう役割のものを表示したい。
けれど、どのように表示するかは、このコンポーネントを使う側で決めてほしい」
という時です。
例えば、スマートデバイスのヘッダーを考えてみてください。
右上にボタンを置くことは、モバイルアプリでもよくあると思います。
Twitterだと、このような感じですね。
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>
- 親コンポーネントから、<navigation-link>タグの中の値、つまり"Your Profile"という文字列が子コンポーネントに渡されます。
- 子コンポーネントは、親コンポーネントから渡された値を、
タグで受け取ります。 - つまり上の例だと、子コンポーネントの
<slot></slot>
の部分に"Your Profile"という文字列が入ります。
最終的に下のように描画されます。
<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>
子コンポーネントの
親コンポーネントの<template>
タグのv-slotで対応するコンテンツが入ります。
slotを使うメリット
- slotを使うことで描画内容を親コンポーネントで決定することができます。)
→上でも触れましたが、例えばpropsを使う場合は、描画内容は子コンポーネントで決めることなります。
そして、propsによって、描画内容を切り替える処理を書かなくてはいけず、
描画内容が増えるたびに、子コンポーネントが肥大してしまいます。
そもそも汎用的なコンポーネントは、ビジネスロジックを持つべきではありません。
汎用的ではなくなってしまいますので。
どんなコンポーネントが「汎用的コンポーネント」かに関しては、
アトミックデザインでいう、Organisms(有機体)以下のコンポーネント{Atoms(原子),Molecules(分子),Organisms(有機体)}は
「汎用的コンポーネント」に該当すると言っていいと思います。(個人的見解だし、ケースバイケースです)
[参考]
Atomic DesignとCSS設計 - Atomic Designとは何か | CodeGrid
スコープ付きslotについて
- slotの中でも理解が困難なのが、スコープ付きslotでした。
- スコープ付きslotとは、slotを使った時に親コンポーネントが子コンポーネントにアクセスする手段です。
- 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という項目があります。
画像は、v-text-fieldコンポーネントの例
以前まで、これが何を意味しているのかよく分かっていなかったのですが、
つまり、このコンポーネントを使う場合に、親コンポーネントからアクセスが可能なデータがここに示されているという訳でした。
追記
別の例を交えて説明した記事をこちらに書いたので、
まだよく分からない方は見てみてください。
【Vue.js】スコープ付きスロットが理解できるように例を交えて考える - Qiita
まとめ
Vue.jsは直感的に理解しやすいものが多いのですが、slotはそれらと比べると直感的とは言えないかもしれません。
そういった理由から、Slotを理解することが、Vue中級者への一歩と言われているようです。
Atomicデザインに基づいたきれいなコンポーネントを設計する上では、slotの理解が必須だということが分かったので、見て見ぬ振りせずきちんと向き合っていきたいと思いました。
[参考]