在我之前的文章 中,有紀錄了基本的元件插槽應用。
那在這邊就介紹更多進階的插槽使用情境。
Slot 簡介
我們可以在自訂的元件的 template 中塞入 的標籤,在這個標籤內部可以塞入你想加入這個元件的內容,就算是 HTML 標籤的內容也可以。
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ---HTML--- <basic-component :title="title"> <a href="#">World</a> </basic-component>
---JavaScript--- const BasicComponent = { template: `<div> {{ title }} <slot></slot> </div>`, props: { title: { type: String, requier: true } } } Vue.component('basic-component', BasicComponent)
|
以上這個範例,我們在 HTML 文本部分的元件中,加入了 a 標籤的內容,那這個 a 標籤的內容就會取代 basic-component 元件的 template 中的 標籤。
Compilation Scope
這部分是在說明 的作用域。
在元件中的 slot 可以取用與其所屬元件相同作用域的其他物件的屬性。但它不能調用其所屬元件的屬性。
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| ---HTML--- <div id="app"> <basic-component :title="title"> {{ msg }} </basic-component> </div>
---JavaScript--- const BasicComponent = { template: `<div> {{ title }} <slot></slot> </div>`, props: { title: { type: String, requier: true } }, data() { msg: 'product' } } Vue.component('basic-component', BasicComponent)
new Vue({ el:'#app', data: { msg: 'book' } })
|
上面這個範例你可以看到在 Vue 元件中有一個 msg 的屬性,而在 basic-component 中也有一個 msg 的屬性,那按照我面在上面的說明,slot 只能取用與其所屬元件相同作用域的屬性,
所以,它的結果會調用到 Vue 元件的 msg 屬性,所以,最終呈現的結果會是 book。 而其所屬元件 basic-component 的 msg 的屬性是取用不到的喔。
以上是 slot 的作用域特性要特別注意。
記得不要將上面這種情境和下面這種情境搞混囉
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ---HTML--- <div id="app"> <basic-component :title="title"> Save </basic-component> </div>
---JavaScript--- const BasicComponent = { template: `<div> {{ title }} <slot> {{ msg }} </slot> // 這會調用到 basic-component 自己的 msg 屬性 </div>`, props: { title: { type: String, requier: true } }, data() { msg: 'product' } } Vue.component('basic-component', BasicComponent)
|
你可以看到這邊這個範例,調用 msg 屬性,是寫在 basic-component 的 template 內部,所以,它會調用到 basic-component 自身的 msg,
而在上面那個例子 msg 是寫在 HTML 文本裡面,所以只能調用跟其所屬元件相同作用域的 Vue 元件的 msg 屬性。
這兩種作用域不要搞混了。 一個是父元件作用域,一個是子元件自己的作用域。
Fallback Content
這個部分是在說明 slot 預設值的設定。
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ---HTML--- <basic-component></basic-component> ---JavaScript--- const BasicComponent = { template: `<div> {{ title }} <slot>{{ msg }}</slot> </div>`, props: { title: { type: String, requier: true }, msg: { type: String, default: 'submit' } } } Vue.component('basic-component', BasicComponent)
|
以上的範例你可以看到在 我們沒有加入任何東西,如果此時你在該元件的 slot 標籤中有放入內容的話,則這個內容就會被當作預設內容呈現在畫面中,
所以,上面這個範例 slot 會呈現 BasicComponent 自己的成員屬性 msg 的內容。 反之,若在 中有放入內容的話, slot 的預設內容就會被取代掉。
Name Slots
這邊是在介紹有為 slot 取名的時候,要怎麼使用它們。
在 Fallback Content 的部分,我們並沒有為 slot 取名,所以,我們無法達成將特定內容塞到相對的 slot 中的效果。而利用為 slot 取名就可以達成這種效果囉。
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ---HTML--- <basic-component> <template v-slot:header>Header</template> <template>My name is Landy</template> <template v-slot:footer>Footer</template> </basic-component>
---JavaScript--- const BasicComponent = { template: `<div class="container"> <header> <slot name="header">This is Header</slot> </header> <main> <slot>This is main</slot> </main> <footer> <slot name="footer">footer</slot> </footer> </div>`, }
|
你可以看到以上的範例,我們有為兩個 slot 分別取名為 header 和 footer,所以,在 parent-component 的狀態下,我們在 template 標籤中加入 v-slot: 後面加入相對應的 slot 名稱後,
我們就可以將特定的內容塞到相對應的 slot 中囉。
而在 main 標籤裡面的 slot 我們就沒有為它特別命名,而在 Vue 的官方文件有提到,這類的 slot 名稱會預設為 default,所以,在 parent-component 的狀態下,那些沒有用 v-slot: 來決定
其內容要塞到哪一個 slot 的內容,就會自動被塞到這種沒有被命名的slot 中囉。
沒有放在指定 slot 中的內容
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ---HTML--- <basic-component> Landy <template v-slot:header>Header</template> <div>Thsi is DIV</div> <a href="#">This is a tag</a> <template v-slot:footer>Footer</template> </basic-component>
---JavaScript--- const BasicComponent = { template: `<div class="container"> <header> <slot name="header">This is Header</slot> </header> <main> <slot>This is main</slot> </main> <footer> <slot name="footer">footer</slot> </footer> </div>`, }
|
你可以看到上面範例中,在 parent-component 中,有一些內容都沒有被 template 包起來,那這種內容會跑到哪裡去呢?
答案是這些內容都會統一被塞到預設的 slot 中,也就是會被塞到沒有被命名的 slot 中,且呈現順序會是由上到下排列。
沒有預設 slot 的內容
今天我們在變換一下上面的範例,如果我們連預設的 slot 的沒有的話,那這些內容沒有被指定放入特定 slot 會跑到哪裡去呢?
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ---HTML--- <basic-component> Landy <template v-slot:header>Header</template> <div>Thsi is DIV</div> <a href="#">This is a tag</a> <template v-slot:footer>Footer</template> </basic-component>
---JavaScript--- const BasicComponent = { template: `<div class="container"> <header> <slot name="header">This is Header</slot> </header> <main> Main </main> <footer> <slot name="footer">footer</slot> </footer> </div>`, }
|
答案會是,在 parent-component 這些沒有被指定放入特定 slot 的內容會直接被移除掉,所以,此範例由上到下呈現的內容會是
Header
Main
Footer
如果有加入v-slot:default呢
如果今天在 parent-component 中,有一個 template 搭配 v-slot:default 的話,那上面範例中那些沒有被指定的內容還會出現嗎?
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| ---HTML--- <basic-component> <a href="#">a tag</a> <template v-slot:header>Header</template> <template v-slot:default> <p>This is default content</p> </template> <template v-slot:footer>Footer</template> </basic-component>
---JavaScript--- const BasicComponent = { template: `<div class="container"> <header> <slot name="header">This is Header</slot> </header> <main> <slot>This is Main</slot> </main> <footer> <slot name="footer">footer</slot> </footer> </div>`, }
|
以上的範例結果是,只會留下 v-slot:default 中的內容塞入 default slot,所以,在 parent-component 中的那個沒有被指定塞入特定 slot 的 a 連結內容,就會直接被移除掉囉。
Scoped Slot
在 slot 有提供一種方法來讓父層作用域可以調用到子層作用域的資料。
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ---HTML--- <basic-component> <template v-slot:header>Header</template> <template v-slot:default="slotProps">{{ slotProps.user.first }}</template> </basic-component>
---JavaScript--- const BasicComponent = { template: `<div class="container"> <header> <slot name="header">This is Header</slot> </header> <main> <slot v-bind:user="UserData">{{ UserData.last }}</slot> </main> </div>`, data () { return { UserData: { first: 'Landy', last: 'Chen' } } } }
|
在以上範例中,我們在子層的預設 slot 中,利用 v-bind 去綁定一個我們自訂義的屬性 user,而此屬性綁定的子層資料為 UserData。
此時,在父層部分,你可以看到,在 v-slot:default 的 parent-component 中,有額外再綁一個我們自訂義的參數叫 slotProps,而透過這個參數
我們就可以去調用我們在預設 slot 中傳出來的 user 屬性,接著再透過這個 user 屬性去使用子層的 UserData 的資料。
Scoped Slot 傳入 JS 表達式
上面的範例我們有看到怎麼使用 v-bind 來將子層資料傳出去給父層使用。
那在 Vue 中還有提供一個更簡便的方式來讓父層取用子曾傳出來的資料,就是用 ES6 的語法將綁定的資料傳出來,並用再父層額外命名一個 props 的名稱了。
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ---HTML--- <basic-component> <template v-slot:header="{ data }">{{ data.first }}</template> <template>Main</template> </basic-component>
===JavaScript--- const BasicComponent = { template: `<div class="container"> <header> <slot name="header" v-bind:data="UserData">{{ UserData.last }}</slot> </header> <main> <slot>This is Main</slot> </main> </div>`, data () { return { UserData: { first: 'Landy', last: 'Chen' } } } }
|
你可以看到在 parent-component 中我們用了v-slot:header=”{ data }” 的 es6 的語法,直接將我們在子層定義的傳出資料的 data 內容,直接取出來,
就可以直接取用裡面的內容囉。 可以讓程式碼看起來比較簡潔。
Dynamic Slot Names
Vue 有提供動態切換 slot 名稱的功能。
ex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| ---HTML--- <basic-component> <template v-slot:header>Header</template> <template v-slot:[DynamicSlot]> 5555 </template> </basic-component>
---JavaScript--- const BasicComponent ={ template: ` <header> <slot name="header">This is Header </slot> </header> <main> <slot>This is Main </slot> </main> ` }
Vue.component('basic-component', BasicComponent)
new Vue({ el: '#app', data: { DynamicSlot: 'default' }
})
|
你可以看到以上範例,我們在 basic-component 上的其中一個 template 綁上了 DynamicSlot ,那這個資料是由 Vue 物件來決定它的內容的,而這邊是把它設為 default ,所以
,在該 template 的內容,會被插入到 basic-component 的 template 中的預設 slot 中,也就是 This is Main 會被 5555 給取代掉。
那今天若把 DynamicSlot 改成 header 的話,你會發現原本在 basic-component 預計要塞入的 Header 內容,會直接被 5555 搶先取代掉了。
由以上這種表現可以知道, v-slot 綁定的動態 slot 的優先序比原本元件中的靜態 slot 還要高。