<template v-slot:activator="{on, attrs}"
是什麼東東?前端筆記
Vuetify
Vue
最近專案需要頻繁地使用 Vuetify,雖然官方文件寫的很用心,也有很多範例,但是我 Vue 的知識還是太淺,看了同事的程式碼才更理解…
2022/03/31
整理:
<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>
比對兩個範例得知如果要有這樣子的效果,必須要使用 <v-dialog>
、<template v-slot:activator="...">
包覆 <input>
並在關閉 <v-dialog>
前插入 <v-date-picker>
。其他的 properties 暫時先不管他,因為其他的看官網文件應該沒問題,但是讓我最好奇的是:到底 <template v-slot:activator="...">
是什麼東東。
一開始我理解 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:
// App.vue
<template>
<div id="app">
<play-slot>
<h1>Hello</h1>
</play-slot>
</div>
</template>
// PlaySlot.vue
<template>
<div>
<slot></slot>
<p>In Play slot</p>
</div>
</template>
沒什麼稀奇,因為 slot wrapper 會把在 parent 中間的 template 插進去。
<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"
,讓程式碼的閱讀性更加。
// 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>
// 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>
編譯後就會得到:
<div>
<div>
<h1>
Hello
</h1>
<p>
In Play Slot
</p>
<h2>
Lun
</h2>
</div>
</div>
除了把資料丟給 parent,<slot>
也可以從 parent 接資料後再把資料丟給 parent:
// 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 作用域。
根據 Vue 的官方文件,scoped slot 底層就像是定義一個單個 argument function 一樣,因此也可以使用解構(destructure)
function (slotProps) {
// ... slot content ...
}
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
也可以寫 default
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>
<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>
<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?
// 把 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 的證明。
所以不使用解構的話,程式碼可以這樣子看:
<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>
activator
也是幕後功成之一根據 Vuetify 的文件,<v-dialog>
的 v-slot:activator
會觸發 <v-dialog>
component(藉由點擊或者其他 event),且 <v-dialog>
會傳事件給 <template>
(事件被包裝成物件,且 key
為 on
)。
如果開發者想要使用怎麼便利的元件,就必須乖乖使用 V-slot:activator
。
v-on
、v-bind
也可以收物件因為 <template>
接到的資料為:
{attrs: {....}, on: {click: callback} }
根據文件,v-on
可以接物件(v-on="{eventName: callbak}"
)。
<!-- object syntax (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
同理,v-bind="attrs"
就是接物件包裝好的 HTML accessibility
屬性。