# [Vuetify] Vuetify 元件中常看到的 `<template v-slot:activator="{on, attrs}"` 是什麼東東?
###### tags: `前端筆記` `Vuetify` `Vue`
最近專案需要頻繁地使用 [Vuetify](https://vuetifyjs.com/en/),雖然官方文件寫的很用心,也有很多範例,但是我 Vue 的知識還是太淺,看了同事的程式碼才更理解...
## ==TODO:==
2022/03/31
整理:
- [7 Secret Patterns Vue Consultants Don’t Want You to Know - Chris Fritz](https://www.youtube.com/watch?v=7lpemgMhi0k&t=1314s)
- [Vuetify, tooltips: what are "on" and "attrs" for?](https://stackoverflow.com/questions/62702245/vuetify-tooltips-what-are-on-and-attrs-for)
## 同事及網路上的使用範例
1. 同事大神的程式碼(使用 popup 效果做出彈跳式地日曆選擇器):
```javascript=
<v-dialog
ref="endDateDialogRef"
v-model="endDateModal"
:return-value.sync="dates.endDate"
:retain-focus="false"
persistent
width="290px"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="dates.endDate"
label="迄日"
append-icon="mdi-calendar"
readonly
v-bind="attrs"
outlined
v-on="on"
/>
</template>
<v-date-picker
v-model="dates.endDate"
>
<v-spacer />
<v-btn
text
color="primary"
@click="endDateModal = false"
>
Cancel
</v-btn>
<v-btn
text
color="primary"
@click="$refs.endDateDialogRef.save(dates.endDate)"
>
OK
</v-btn>
</v-date-picker>
</v-dialog>
```
2. 網路上找到的[範例](https://codepen.io/abdelsalam-shahlol/pen/poEYJqN),一樣是點擊後出現 popup 的日曆
比對兩個範例得知如果要有這樣子的效果,必須要使用 `<v-dialog>`、`<template v-slot:activator="...">` 包覆 `<input>` 並在關閉 `<v-dialog>` 前插入 `<v-date-picker>`。其他的 properties 暫時先不管他,因為其他的看官網文件應該沒問題,但是讓我最好奇的是:**到底 `<template v-slot:activator="...">` 是什麼東東。**
## Scoped slot(作用域插槽)
一開始我理解 slot 就認為它知能做一個像是包裝紙(wrapper)的東西,讓 parent 可以把 parent 的 template 給 child(也就是 slot),沒想到 slot 還有另一個很酷的功能,也就是標題的 scoped slot(作用域插槽)。
透過 scoped slot 讓開發者也可以把 slot 所屬的 component 有辦法將自身的 state 丟給 parent 使用(雖然語法看起來很怪...)。
假設目前我有兩個 components,App.vue(parent) 以及 PlaySlot.vue(child),在 child 中用 slot,把該 component 當作 wrapper:
```javascript=
// App.vue
<template>
<div id="app">
<play-slot>
<h1>Hello</h1>
</play-slot>
</div>
</template>
```
```javascript=
// PlaySlot.vue
<template>
<div>
<slot></slot>
<p>In Play slot</p>
</div>
</template>
```
沒什麼稀奇,因為 slot wrapper 會把在 parent 中間的 template 插進去。
```htmlembedded=
<div>
<div>
<h1>Hello</h1>
<p>In Play slot</p>
</div>
</div>
```
那如果 child 自己也有 state 想要傳給 parent 呢?
`<slot name="something" :keyName="value" />` 如 parent 傳資料給 child,可用像 props 的語法 `:keyName="value"`,也可以給名字 `name=""` `<template v-slot:Name="slotProps"`,讓程式碼的閱讀性更加。
```javascript=
// PlaySlot.vue
<template>
<div>
<slot></slot>
<p>In Play slot</p>
// slot 也可以把 child state 向上傳
<slot name="activator" :user="user"></slot>
// === <slot name="activator" v-bind:user="user">
</div>
</template>
<script>
// child 自己的 state
export default {
data() {
return {
user: 'Lun'
}
}
}
</script>
```
```javascript=
// App.vue
<template>
<div id="app">
<play-slot>
<h1>Hello</h1>
<template v-slot:activator="slotProps">
<h2>{{ slotPrps.user }}</h2>
</template>
</play-slot>
</div>
</template>
```
編譯後就會得到:
```htmlembedded=
<div>
<div>
<h1>
Hello
</h1>
<p>
In Play Slot
</p>
<h2>
Lun
</h2>
</div>
</div>
```
除了把資料丟給 parent,`<slot>` 也可以從 parent 接資料後再把資料丟給 parent:
```javascript=
// App.vue
<div>
<play-slot :number="number">
<h1>Hello</h1>
<template v-slot:test="props">
// 從 parent 得到的 number 又藉由 slot 丟到 parent 了
<h2>{{ props.number }}</h2>
</template>
</play-slot>
</div>
// PlaySlot.vue
<template>
<div>
<slot></slot>
<p>{{ number }}</p>
<p>In Play slot</p>
<slot name="test" v-bind:number="number"></slot>
</div>
</template>
```
所以 scoped slot 不僅可以丟 child 自己本身的 state 到 parent,也可以從 parent 拿到 state 後再藉由 scoped slot 丟還給 parent。
但不要忘記,有一條鐵的紀律要記住:
> Everything in the parent template is compiled in parent scope; everything in the child template is compiled in the child scope.
> 所有出現在 parent template 會被編譯為 parent 作用域,反之,所有出現在 child template 就回被編譯為 child 作用域。
## Scoped slot 從 child 傳 state 到 parent 就像是 function
根據 Vue 的[官方文件](https://v2.vuejs.org/v2/guide/components-slots.html?redirect=true#Destructuring-Slot-Props),scoped slot 底層就像是定義一個單個 argument function 一樣,因此也可以使用解構(destructure)
```javascript=
function (slotProps) {
// ... slot content ...
}
```
```javascript=
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
```
也可以寫 default
```javascript=
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>
```
## 回到 Vuetify 的範例
```javascript=
<v-dialog
v-model="endDateModal"
...
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="dates.endDate"
v-bind="attrs"
v-on="on"
...
/>
</template>
<v-date-picker
v-model="dates.endDate"
>
....
</v-date-picker>
</v-dialog>
```
### 1. `<template>` 是從 parent 取得 props 後再藉由 scoped slot 丟回 parent
如前面所述,這個例子的 `<template v-slot:activator="{on, attrs}">` 是一個 scoped slot,所以它其實就是把 child 本身的 state / 從 parent 拿到的 state 拿回給 parent,讓 parent template 可以取得需要的 state。
#### 要怎麼確定 `<template>` 真的有拿 parent 的 state?
```javascript=
// 把 template 的解構拿掉,換成一般的樣子
<v-dialog>
<template v-slot:activator="slotProps">
<v-text-field
v-model="dates.endDate"
v-bind="attrs"
v-on="on"
@click="test(slotProps)"
...
/>
</template>
</v-dialog>
<srcipt>
...
test(slotProps) {
console.log(slotProps)
}
</script>
```
執行完確實會出現 `slotProps`,代表是 scoped slot 傳上去給 parent 的證明。

所以不使用解構的話,程式碼可以這樣子看:
```javascript=
<v-dialog
v-model="endDateModal"
...
>
// 中規中矩,不適用 destructure
<template v-slot:activator="slotProps">
<v-text-field
v-model="dates.endDate"
v-bind="slotProps.attrs"
v-on="slotProps.on"
...
/>
</template>
<v-date-picker
v-model="dates.endDate"
>
....
</v-date-picker>
</v-dialog>
```
### 2. `activator` 也是幕後功成之一
根據 Vuetify 的[文件](https://vuetifyjs.com/en/api/v-dialog/#events),`<v-dialog>` 的 `v-slot:activator` 會觸發 `<v-dialog>` component(藉由點擊或者其他 event),且 `<v-dialog>` 會傳事件給 `<template>`(事件被包裝成物件,且 `key` 為 `on`)。
如果開發者想要使用怎麼便利的元件,就必須乖乖使用 `V-slot:activator`。
### 3. `v-on`、`v-bind` 也可以收物件
因為 `<template>` 接到的資料為:
```javascript=
{attrs: {....}, on: {click: callback} }
```
根據[文件](https://v2.vuejs.org/v2/api/?redirect=true#v-on),`v-on` 可以接物件(`v-on="{eventName: callbak}"`)。
```javascript=
<!-- object syntax (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
```
[同理](https://v2.vuejs.org/v2/api/?redirect=true#v-bind),`v-bind="attrs"` 就是接物件包裝好的 `HTML accessibility` 屬性。
## 參考資料
1. 程式範例
- [Date Picker inside dialog - Vuetify](https://codepen.io/abdelsalam-shahlol/pen/poEYJqN)
- [Vuetify Inline Datatable Editor](https://codepen.io/Jayesh_v/pen/wvgjMva)
2. 文章
- [Vuetify组件中常见的v-slot:activator=“{ on, attrs }“是什么意思?](https://blog.csdn.net/weixin_44710964/article/details/107428727)
- [Meaning of v-slot:activator="{ on }"](https://stackoverflow.com/questions/55188478/meaning-of-v-slotactivator-on/55194478)