# [ 想入門,我陪你 ] Re Vue 重頭說起|Day 12:組件與自定 Slot ###### tags: `Vue`、`Re:Vue 重頭說起`、`Alex 宅幹嘛` :::warning 版本要注意 In 2.6.0, we introduced a new unified syntax (the v-slot directive) for named and scoped slots. It replaces the slot and slot-scope attributes, which are now deprecated, but have not been removed and are still documented here. The rationale for introducing the new syntax is described in this RFC. ::: ## Slot Content Slot:透過父層就可以決定子代Component的內容 沒放Slot: ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 12</title> </head> <body> <div id="app"> <basic-component title="Hello"> World </basic-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div> {{ title }} </div> `, props: { title: { type: String, required: true, } } }) </script> </body> </html> ``` > 畫面只會出現 Hello 加入 Slot: ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 12</title> </head> <body> <div id="app"> <basic-component title="Hello"> World </basic-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div> {{ title }} <slot></slot> </div> `, props: { title: { type: String, required: true, } } }) </script> </body> </html> ``` > 畫面會出現 Hello World => Solt 可以顯示 DOM, Component 或 資料 Component 跟 Vue 實體都有 data msg ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 12</title> </head> <body> <div id="app"> <basic-component title="Hello"> {{ msg } <!-- Alex --> </basic-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div> {{ title }} <slot></slot> </div> `, data: { return { msg: 'World' } } props: { title: { type: String, required: true, } } }) new Vue({ el: '#app', data: { return { msg: 'Alex' } } }) </script> </body> </html> ``` > 因為**外層作用域**,印出 Hello Alex ![](https://i.imgur.com/bpEP8Wt.png) 雖然BasicComponent內部有接 <slot></slot>(World),但Vue不會讀取 ![](https://i.imgur.com/ukQYdzj.png) Vue實體移除 msg data 會噴錯 ![](https://i.imgur.com/vcGxWL9.jpg) :::success 總結: Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope. 外層的資料在外層compiled,反之 ::: ## Fallback Content 預設值(23:20) 假設外面有傳值,slot就用外面的值,沒有就用slot內給的預設得 ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 12</title> </head> <body> <div id="app"> <basic-component title="Hello"> <!-- 移除內文,會讓slot有何影響? --> </basic-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> // slot 內加入 msg Vue.component('BasicComponent', { template: ` <div> {{ title }} <slot>{{ msg }}</slot> </div> `, data: { return { msg: 'World' } } props: { title: { type: String, required: true, } } }) new Vue({ el: '#app', data: { return { msg: 'Alex' } } }) </script> </body> </html> ``` > Hello World ## Named Slots slot 可以重複定義,如下 ```htmlmixed= <div> {{ title }} {{ title }} <slot>{{ msg }}</slot> {{ title }} <slot>{{ msg }}</slot> {{ title }} <slot>{{ msg }}</slot> </div> ``` ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 12</title> </head> <body> <div id="app"> <basic-component title="Hello"> {{msg}} </basic-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> // slot 內加入 msg Vue.component('BasicComponent', { template: ` <div class="container"> {{title}} <header> <!-- We want header content here --> <slot name="header">This is Header</slot> </header> <main> <!-- We want main content here --> <slot>This is Main</slot> </main> <footer> <!-- We want footer content here --> <slot name="footer">This is Footer</slot> </footer> </div> `, data: { return { msg: 'World' } } props: { title: { type: String, required: true, } } }) new Vue({ el: '#app', data: { return { msg: 'Alex' } } }) </script> </body> </html> ``` > Main 的 slot 沒有給 name 所以用 default 的 Alex (msg),但header與 footer沒東西 ![](https://i.imgur.com/wbCcQSL.png) ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 12</title> </head> <body> <div id="app"> <basic-component title="Hello"> <template v-slot:header> Header </template> {{msg}} <template></template> </basic-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> // slot 內加入 msg Vue.component('BasicComponent', { template: ` <div class="container"> {{title}} <header> <!-- We want header content here --> <slot name="header">This is Header</slot> </header> <main> <!-- We want main content here --> <slot>This is Main</slot> </main> <footer> <!-- We want footer content here --> <slot name="footer">This is Footer</slot> </footer> </div> `, data: { return { msg: 'World' } } props: { title: { type: String, required: true, } } }) new Vue({ el: '#app', data: { return { msg: 'Alex' } } }) </script> </body> </html> ``` 為何Vue設計是 v-slot:header 而不是 v-slot="header"[RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md#detailed-design) ![](https://i.imgur.com/17XXRYz.png) 好處 34:35 問題ㄧ: 請問 slot 中間內容會長什麼樣子? ![](https://i.imgur.com/ZUQc7TZ.jpg) 答案一: 1234,全部依序塞到預設slot ![](https://i.imgur.com/f7K0God.png) 問題二: 假設,basic-component中內容不變,但是tempate改變,請問 slot 中間內容會長什麼樣子? ![](https://i.imgur.com/q8WBQf1.jpg) 答案二: 沒有指定位置的內容,會全部依序放在預設的slot,但加設沒有預設的slot,就是不能塞,所以 component內的東西會被丟掉,所以會出現 <main>This is Main</main> > name-slot 比較好用,適合用在 content layout 預設slot沒命名,其等同於 default 問題三: 畫面會出現 Alex / 222 / 555 / 333 / 444? (main 改回 slot) ![](https://i.imgur.com/dvgC1WP.jpg) 答案三: 555 假設什麼都沒有,會全部依序放在預設的slot(下圖) ![](https://i.imgur.com/DefW5wS.jpg) 若有指定 default slot(貴族金牌),其他的就會被排除掉 ![](https://i.imgur.com/dmlvKdw.jpg) 問題四: 兩個 default slot? ![](https://i.imgur.com/biFo4Z2.jpg) 答案四: 555,會後面覆蓋前面default > v-slot 只能加在 <template> 上,但有一個[例外](https://vuejs.org/v2/guide/components-slots.html#Abbreviated-Syntax-for-Lone-Default-Slots) (**v-slot:default**可以綁在 component上), unlike the deprecated slot attribute. ## Scoped Slots(1:01:28) > In 2.6.0, 取代 **slot-scope** 在 slot 上用 v-bind綁定 ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 12</title> </head> <body> <div id="app"> <basic-component title="Hello"> <template v-slot:header> Header </template> <template v-slot:default="slotProps"> {{ slotProps.data.first }} // Alex </template> <template v-slot:heade> Footer </template> </basic-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> // slot 內加入 msg Vue.component('BasicComponent', { template: ` <div class="container"> {{title}} <header> <!-- We want header content here --> <slot name="header">This is Header</slot> </header> <main> <!-- We want main content here --> <!-- 讓 insideData 在 slot 這作用域可以給外部人使用 --> <slot v-bind:data="insideData"> Mr. {{ insideData.last }}</slot> </main> <footer> <!-- We want footer content here --> <slot name="footer">This is Footer</slot> </footer> </div> `, data: { return { msg: 'World', insideData: { first: 'Alex', last: 'Chen' } } } props: { title: { type: String, required: true, } } }) new Vue({ el: '#app', data: { return { msg: 'Alex' } } }) </script> </body> </html> ``` ### Abbreviated Syntax for Lone Default Slots ```htmlmixed= <template v-slot="slotProps"> {{ slotProps.data.first }} // Alex </template> ``` > 移除 default 仍可以work ```htmlmixed= <!-- INVALID, will result in warning --> <current-user v-slot="slotProps"> {{ slotProps.user.firstName }} <template v-slot:other="otherSlotProps"> slotProps is NOT available here </template> </current-user> ``` > 作用域內再塞作用域就會無效 ```htmlmixed= <current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> <template v-slot:other="otherSlotProps"> ... </template> </current-user> ``` > multiple slots => 不同名稱 ### Destructuring Slot Props ```htmlmixed= <template v-slot="{ data }"> {{ data.first }} // Alex </template> ``` ### Dynamic Slot Names 可以透過參數、變數控制內容要差在哪 ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 12</title> </head> <body> <div id="app"> <basic-component title="Hello"> <template v-slot:header> Header </template> <template v-slot:default> 555 </template> <template v-slot:footer> Footer </template> <div>333</div> <template v-slot:[slot]> 666 </template> </basic-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> // slot 內加入 msg Vue.component('BasicComponent', { template: ` <div class="container"> {{title}} <header> <slot name="header">This is Header</slot> </header> <main> <slot v-bind:data="insideData"> Mr. {{ insideData.last }}</slot> </main> <footer> <slot name="footer">This is Footer</slot> </footer> </div> `, data: { return { msg: 'World', insideData: { first: 'Alex', last: 'Chen' } } } props: { title: { type: String, required: true, } } }) new Vue({ el: '#app', data: { return { msg: 'Alex', slot: 'default' } } }) </script> </body> </html> ``` 當 default 清空會發生什麼事? ![](https://i.imgur.com/YhpjzvJ.png) 因為免死金牌拿掉了,所以會轉給上一個人 ![](https://i.imgur.com/hB4vD9b.png) ![](https://i.imgur.com/Ifxn5ft.png) ![](https://i.imgur.com/OuPtLs7.png) ### Named Slots Shorthand `v-slot:header` = `#header` ### Other Examples v-for with slot ```htmlmixed= <ul> <li v-for="todo in filteredTodos" v-bind:key="todo.id" > <!-- 每個 todoList 就是一個 slot We have a slot for each todo, passing it the `todo` object as a slot prop. --> <slot name="todo" v-bind:todo="todo"> <!-- Fallback content --> {{ todo.text }} </slot> </li> </ul> ``` 把 todo list 的完成勾勾流到外面來做,好處是slot內的內容是list顯示層,勾勾邏輯不用寫在裡面,透過外面來管理(1:27:00) ```htmlmixed= <todo-list v-bind:todos="todos"> <template v-slot:todo="{ todo }"> <span v-if="todo.isComplete">✓</span> {{ todo.text }} </template> </todo-list> ``` > 更多複雜範例應用: However, even this barely scratches the surface of what scoped slots are capable of. For real-life, powerful examples of scoped slot usage, we recommend browsing libraries such as [Vue Virtual Scroller](https://github.com/Akryum/vue-virtual-scroller), [Vue Promised](https://github.com/posva/vue-promised), and [Portal Vue](https://github.com/LinusBorg/portal-vue). ### Deprecated Syntax 棄用的功能