Vue.jsでスロットを使用する
公開: 2022-03-10Vue 2.6の最近のリリースでは、スロットを使用するための構文がより簡潔になりました。 このスロットへの変更により、Vueベースのプロジェクトに再利用性、新機能、およびより明確な読みやすさを提供するスロットの潜在的な力を発見することに再び興味を持ちました。 スロットは本当に何ができるのですか?
Vueを初めて使用する場合、またはバージョン2.6からの変更を確認していない場合は、読み進めてください。 おそらくスロットについて学ぶための最良のリソースはVue自身のドキュメントですが、ここで要約を示します。
スロットとは何ですか?
スロットは、厳密な親子関係以外の方法でコンポーネントを構成できるようにするVueコンポーネントのメカニズムです。 スロットは、コンテンツを新しい場所に配置したり、コンポーネントをより一般的にしたりするための手段を提供します。 それらを理解する最良の方法は、それらが実際に動作しているのを見ることです。 簡単な例から始めましょう:
// frame.vue <template> <div class="frame"> <slot></slot> </div> </template>
このコンポーネントにはラッパーdiv
があります。 div
がコンテンツの周りに文体的なフレームを作成するためにあるとしましょう。 このコンポーネントは、一般的に使用して、必要なコンテンツの周りにフレームをラップすることができます。 それを使用するとどのように見えるか見てみましょう。 ここでのframe
コンポーネントとは、上記で作成したコンポーネントを指します。
// app.vue <template> <frame><img src="an-image.jpg"></frame> </template>
開始フレームタグと終了frame
タグの間にあるコンテンツは、 slot
があるframe
コンポーネントに挿入され、 slot
タグが置き換えられます。 これが最も基本的な方法です。 次のように入力するだけで、スロットに入れるデフォルトのコンテンツを指定することもできます。
// frame.vue <template> <div class="frame"> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>
したがって、代わりに次のように使用すると、次のようになります。
// app.vue <template> <frame /> </template>
「ここに何も指定されていない場合、これはデフォルトのコンテンツです」というデフォルトのテキストが表示されますが、以前と同じように使用すると、デフォルトのテキストはimg
タグによって上書きされます。
複数/名前付きスロット
コンポーネントに複数のスロットを追加できますが、追加する場合は、1つを除くすべてに名前を付ける必要があります。 名前のないスロットがある場合は、それがデフォルトのスロットです。 複数のスロットを作成する方法は次のとおりです。
// titled-frame.vue <template> <div class="frame"> <header><h2><slot name="header">Title</slot></h2></header> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>
同じデフォルトのスロットを維持しましたが、今回は、タイトルを入力できるheader
という名前のスロットを追加しました。 次のように使用します。
// app.vue <template> <titled-frame> <template v-slot:header> <!-- The code below goes into the header slot --> My Image's Title </template> <!-- The code below goes into the default slot --> <img src="an-image.jpg"> </titled-frame> </template>
前と同じように、デフォルトのスロットにコンテンツを追加する場合は、 titled-frame
コンポーネント内に直接配置します。 ただし、名前付きスロットにコンテンツを追加するには、 v-slot
ディレクティブを使用してコードをtemplate
タグでラップする必要がありました。 v-slot
後にコロン( :
を追加してから、コンテンツを渡すスロットの名前を書き込みます。 v-slot
はVue2.6の新機能であるため、古いバージョンを使用している場合は、非推奨のスロット構文に関するドキュメントを読む必要があります。
スコープスロット
もう1つ知っておく必要があるのは、スロットがデータ/関数を子に渡すことができるということです。 これを示すために、スロットを備えたまったく異なるサンプルコンポーネントが必要になります。これは、前のコンポーネントよりもさらに工夫されています。現在のユーザーに関するデータをスロットに提供するコンポーネントを作成して、ドキュメントからサンプルをコピーしてみましょう。
// current-user.vue <template> <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> </template> <script> export default { data () { return { user: ... } } } </script>
このコンポーネントには、ユーザーに関する詳細を含むuser
というプロパティがあります。 デフォルトでは、コンポーネントにはユーザーの名前が表示されますが、 v-bind
を使用してユーザーデータをスロットにバインドしていることに注意してください。 これで、このコンポーネントを使用して、ユーザーデータをその子孫に提供できます。
// app.vue <template> <current-user> <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template> </current-user> </template>
スロットに渡されたデータにアクセスするには、スコープ変数の名前をv-slot
ディレクティブの値で指定します。
ここで注意すべき点がいくつかあります。
-
default
の名前を指定しましたが、デフォルトのスロットには指定する必要はありません。 代わりに、v-slot="slotProps"
を使用することもできます。 - 名前として
slotProps
を使用する必要はありません。 あなたはそれを好きなように呼ぶことができます。 - デフォルトのスロットのみを使用している場合は、その内部
template
タグをスキップして、v-slot
ディレクティブをcurrent-user
タグに直接配置できます。 - 単一の変数名を使用する代わりに、オブジェクトの破棄を使用して、スコープスロットデータへの直接参照を作成できます。 つまり、
v-slot="slotProps"
v-slot="{user}"
を使用してから、slotProps.user
の代わりにuser
を直接使用できます。
これらのメモを考慮に入れると、上記の例は次のように書き直すことができます。
// app.vue <template> <current-user v-slot="{user}"> {{ user.firstName }} </current-user> </template>
覚えておくべきいくつかの事柄:
-
v-bind
ディレクティブを使用して複数の値をバインドできます。 したがって、この例では、user
だけでなくそれ以上のことを行うことができます。 - スコープスロットに関数を渡すこともできます。 多くのライブラリはこれを使用して、後で説明するように再利用可能な機能コンポーネントを提供します。
-
v-slot
のエイリアスは#
です。 したがって、v-slot:header="data"
と書く代わりに、#header="data"
書くことができます。 スコープスロットを使用していない場合は、v-slot:header
代わりに#header
を指定することもできます。 デフォルトスロットについては、エイリアスを使用するときにdefault
の名前を指定する必要があります。 つまり、#="data"
= "data"ではなく#default="data"
と記述する必要があります。
ドキュメントから学ぶことができるいくつかのマイナーなポイントがありますが、それはこの記事の残りの部分で話していることを理解するのに十分なはずです。
スロットで何ができますか?
スロットは単一の目的のために構築されたわけではありません。少なくともそうであれば、スロットは、さまざまなことを行うための強力なツールになるという当初の意図をはるかに超えて進化しました。
再利用可能なパターン
コンポーネントは常に再利用できるように設計されていますが、カスタマイズするために必要なprops
の数が多すぎるか、必要になるため、単一の「通常の」コンポーネントで強制するのは実用的ではありません。コンテンツの大部分と潜在的に他のコンポーネントをprops
に通します。 スロットを使用して、パターンの「外側」の部分を囲み、他のHTMLやコンポーネントをその内側に配置して「内側」の部分をカスタマイズできるようにします。これにより、スロットを備えたコンポーネントでパターンを定義し、コンポーネントをスロットは一意である必要があります。
最初の例では、ボタンという簡単なものから始めましょう。 あなたとあなたのチームがBootstrap *を使用していると想像してください。 Bootstrapを使用すると、ボタンは多くの場合、基本の `btn`クラスと、` btn-primary`などの色を指定するクラスでストラップされます。 `btn-lg`などのサイズクラスを追加することもできます。
*私はあなたがこれをすることを奨励も落胆もしません、私は私の例のために何かが必要でした、そしてそれはかなりよく知られています。
ここで、簡単にするために、アプリ/サイトが常にbtn-primary
とbtn-lg
を使用すると仮定します。 ボタンに3つのクラスすべてを常に書き込む必要はありません。あるいは、ルーキーが3つすべてを実行することを覚えているとは思わないかもしれません。 その場合、これら3つのクラスすべてを自動的に含むコンポーネントを作成できますが、コンテンツのカスタマイズをどのように許可しますか? button
タグにはあらゆる種類のHTMLを含めることができるため、 prop
は実用的ではありません。したがって、スロットを使用する必要があります。
<!-- my-button.vue --> <template> <button class="btn btn-primary btn-lg"> <slot>Click Me!</slot> </button> </template>
これで、どこでも好きなコンテンツで使用できます。
<!-- somewhere else, using my-button.vue --> <template> <my-button> <img src="/img/awesome-icon.jpg"> SMASH THIS BUTTON TO BECOME AWESOME FOR ONLY $500!!! </my-button> </template>
もちろん、ボタンよりもはるかに大きなもので行くことができます。 Bootstrapに固執して、モーダル、または少なくともHTML部分を見てみましょう。 機能については説明しません…まだです。
<!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <slot name="footer"></slot> </div> </div> </div> </div> </template>
それでは、これを使用しましょう:
<!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <template #footer> <em>Now back to your regularly scheduled app usage</em> </template> </my-modal> </template>
上記のタイプのスロットのユースケースは明らかに非常に便利ですが、さらに多くのことができます。
機能の再利用
Vueコンポーネントは、HTMLとCSSだけではありません。 これらはJavaScriptで構築されているため、機能性も重視しています。 スロットは、機能を1回作成し、それを複数の場所で使用する場合に役立ちます。 モーダルの例に戻り、モーダルを閉じる関数を追加しましょう。
<!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <!-- using `v-bind` shorthand to pass the `closeModal` method to the component that will be in this slot --> <slot name="footer" :closeModal="closeModal"></slot> </div> </div> </div> </div> </template> <script> export default { //... methods: { closeModal () { // Do what needs to be done to close the modal... and maybe remove it from the DOM } } } </script>
このコンポーネントを使用すると、モーダルを閉じることができるボタンをフッターに追加できます。 通常、Bootstrapモーダルの場合、ボタンにdata-dismiss="modal"
を追加するだけで済みますが、このモーダルコンポーネントにスロットされるコンポーネントからBootstrap固有のものを非表示にします。 したがって、私たちは彼らに呼び出すことができる関数を渡します、そして彼らはBootstrapの関与について賢明ではありません:
<!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <!-- pull in `closeModal` and use it in a button's click handler --> <template #footer="{closeModal}"> <button @click="closeModal"> Take me back to the app so I can be awesome </button> </template> </my-modal> </template>
レンダーレスコンポーネント
そして最後に、スロットの使用について知っていることを利用して、再利用可能な機能を渡し、実質的にすべてのHTMLを削除して、スロットを使用することができます。 これが本質的にレンダリングレスコンポーネントです。HTMLなしで機能のみを提供するコンポーネントです。
ルート要素の必要性をなくすためにテンプレートを使用するのではなく、 render
関数を作成する必要があるため、コンポーネントを本当にレンダリングレスにするのは少し難しいかもしれませんが、常に必要なわけではありません。 ただし、最初にテンプレートを使用できる簡単な例を見てみましょう。
<template> <transition name="fade" v-bind="$attrs" v-on="$listeners"> <slot></slot> </transition> </template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>
これは、JavaScriptが含まれていないため、レンダーレスコンポーネントの奇妙な例です。 これは主に、組み込みのレンダーレス関数transition
の事前構成された再利用可能なバージョンを作成しているためです。
はい、Vueにはレンダリングレスコンポーネントが組み込まれています。 この特定の例は、Cristi Joraによる再利用可能なトランジションに関する記事から抜粋したもので、アプリケーション全体で使用されるトランジションを標準化できるレンダリングレスコンポーネントを作成する簡単な方法を示しています。 Cristiの記事はさらに深く掘り下げられ、再利用可能なトランジションのより高度なバリエーションを示しているので、チェックすることをお勧めします。
他の例では、Promiseのさまざまな状態(保留中、正常に解決済み、失敗)の間に表示されるものの切り替えを処理するコンポーネントを作成します。 これは一般的なパターンであり、多くのコードを必要としませんが、再利用性のためにロジックが引き出されていない場合、多くのコンポーネントが混乱する可能性があります。
<!-- promised.vue --> <template> <span> <slot name="rejected" v-if="error" :error="error"></slot> <slot name="resolved" v-else-if="resolved" :data="data"></slot> <slot name="pending" v-else></slot> </span> </template> <script> export default { props: { promise: Promise }, data: () => ({ resolved: false, data: null, error: null }), watch: { promise: { handler (promise) { this.resolved = false this.error = null if (!promise) { this.data = null return } promise.then(data => { this.data = data this.resolved = true }) .catch(err => { this.error = err this.resolved = true }) }, immediate: true } } } </script>
では、ここで何が起こっているのでしょうか。 まず、 Promise
であるpromise
という小道具を受け取っていることに注意してください。 watch
セクションでは、Promiseの変更を監視し、変更された場合(または、 immediate
プロパティのおかげでコンポーネントの作成直後)、状態をクリアしてから呼び出しthen
Promiseをcatch
して、正常に終了したとき、または失敗します。
次に、テンプレートに、状態に基づいて異なるスロットを表示します。 テンプレートを使用するためにルート要素が必要だったため、真にレンダリングレスに保つことができなかったことに注意してください。 関連するスロットスコープにもdata
とerror
を渡します。
そして、これが使用されている例です:
<template> <div> <promised :promise="somePromise"> <template #resolved="{ data }"> Resolved: {{ data }} </template> <template #rejected="{ error }"> Rejected: {{ error }} </template> <template #pending> Working on it... </template> </promised> </div> </template> ...
somePromise
をレンダーレスコンポーネントに渡します。 終了するのを待っている間、 pending
のスロットのおかげで「Workingonit…」と表示されます。 成功すると、「Resolved:」と解像度の値が表示されます。 失敗した場合は、「拒否:」と拒否の原因となったエラーが表示されます。 これで、このコンポーネントが独自の再利用可能なコンポーネントに引き出されるため、このコンポーネント内のPromiseの状態を追跡する必要がなくなります。
では、 promised.vue
のスロットをラップするspan
について何ができるでしょうか。 これを削除するには、 template
部分を削除し、コンポーネントにrender
関数を追加する必要があります。
render () { if (this.error) { return this.$scopedSlots['rejected']({error: this.error}) } if (this.resolved) { return this.$scopedSlots['resolved']({data: this.data}) } return this.$scopedSlots['pending']() }
ここでは、それほどトリッキーなことは何も起こっていません。 いくつかのif
ブロックを使用して状態を見つけ、正しいスコープのスロットを返し( this.$scopedSlots['SLOTNAME'](...)
を介して)、関連するデータをスロットスコープに渡します。 テンプレートを使用していない場合は、JavaScriptをscript
タグから取り出して.js
ファイルに挿入するだけで、 .vue
ファイル拡張子の使用をスキップできます。 これにより、これらのVueファイルをコンパイルするときにパフォーマンスがわずかに向上するはずです。
この例は、vue-promisedの簡略化されたわずかに調整されたバージョンです。これは、いくつかの潜在的な落とし穴をカバーしているため、上記の例を使用するよりもお勧めします。 レンダリングレスコンポーネントのすばらしい例は他にもたくさんあります。 Baleadaは、このような便利な機能を提供するレンダリングレスコンポーネントでいっぱいのライブラリ全体です。 画面に表示されているものに基づいてリストアイテムのレンダリングを制御するためのvue-virtual-scrollerや、DOMの完全に異なる部分にコンテンツを「テレポート」するためのPortalVueもあります。
私は出ています
Vueのスロットは、コンポーネントベースの開発をまったく新しいレベルに引き上げます。スロットの優れた使用方法を数多く示しましたが、他にも数え切れないほどの方法があります。 あなたはどんな素晴らしいアイデアを思いつくことができますか? スロットはどのようにアップグレードできると思いますか? もしあれば、あなたのアイデアをVueチームに持ってきてください。 神の祝福と幸せなコーディング。