# [ 想入門,我陪你 ] 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 棄用的功能