上一篇說了 DefineOptions、defineModel、Props 的響應式解構和從外部匯入型別 這幾個新功能,但是沒有說Generic、defineSlots等,這是因為還沒有完全搞清楚可以用在什麼地方。折騰了幾天終於弄清楚了。
這還要從 TS 的泛型說起。
泛型僅僅只是表達傳啥都行嗎?當然不是,因為js原生就支援「泛型」,本來就啥都可以傳的。
泛型的目的是——約束!泛型相當於制定了一個白名單,名單裡面的型別可以傳,不在名單裡面的不可以傳。
TS 的泛型可以幫助我們更準確的推斷型別,從而在編寫程式碼的時候,可以有更準確的提示和提供驗證依據。
元件的props可以設定各種型別,那麼如果想用泛型的話,要如何設定呢?這就需要使用 Generic:
<script setup lang="ts" generic="T extends {name: string} ">
const props = defineProps<{
list: T[], // 泛型的方式
list2: number[], // 只能是 number 型別的陣列
list3: Array<any>, // 任意型別的陣列
name: string,
person: {
name: string
}
}>()
console.log('props-ts:\n', props)
這裡定義了幾個屬性,第一個使用了泛型,第二個是 number[],第三個是任意型別的陣列。
我們來看看不同型別的提示資訊:
Array<any> 提示的時候,無法獲知具體的型別。
number[] 必須和設定的型別完全一致。
T[] 可以根據傳入的型別做出對應的提示
傳入 {name: string}
傳入 {name: string, age: number}
型別不匹配的提示
對比一下,我們可以發現,使用泛型可以準確的推斷型別,在模板裡面可以有更準確的提示,如果型別不合格,可以有提示資訊。
這樣在編寫程式碼的時候可以避免低階錯誤。
defineSlots 是做什麼的呢,是定義插槽還是獲取插槽?準確的說,是定義作用域插槽的props的型別(支援泛型),然後返回父元件傳入的插槽。
在元件裡面定義兩個插槽,一個是匿名插槽,一個是作用域插槽(col),
定義一個 list 的屬性,傳入一個陣列,然後遍歷這個陣列,建立一組列表,列表內使用作用域插槽。
通過作用域插槽的props把陣列元素傳遞給父元件:(好像有點繞)
<script setup lang="ts" generic="T extends Object ">
const props = defineProps<{
list: T[], // 泛型的方式
}>()
const slot = defineSlots<{
default(props: any): any,
col(props:
{
row: T,
index: number
}): any
}>()
console.log('slot:\n', slot)
<template>
<!--匿名插槽-->
<slot></slot>
<div v-for="(item, index) in list" :key="index">
<!--作用域插槽-->
<slot name="col" :row="item" :index="index" ></slot>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
// 載入子元件
import ts from './20-ts.vue'
// 定義陣列
const list2 = reactive([
{
name: '11',
age: 10
},
{
name: '66',
age: 10
}
])
</script>
<template>
<ts :list="list2" > <!--傳入資料列表-->
<h1>測試插槽</h1>
<template #col="{ item, index }"> <!--用解構的方式獲取-->
序號:{{ index }}<br> <!--其實這裡是迴圈-->
內容:{{ item }}
</template>
</ts>
</template>
UI庫裡的 table 元件一般都會支援這樣的插槽,以便於靈活設定列表,比如 el-table 的 el-table-column:
(來自官網範例程式碼)
<el-table :data="tableData" style="width: 100%">
<el-table-column label="日期" width="180">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-icon><timer /></el-icon>
<span style="margin-left: 10px">{{ scope.row.date }}</span>
</div>
</template>
</el-table-column>
...
</el-table>
這裡的 default 就是一個匿名作用域插槽,可以通過scope.row
獲得每一行的資料。
defineEmits 是定義事件的一種快捷表達方式,也是一種語法糖,這個和 defineModel 有重合的地方,那就是 v-model 的 update:modelValue
的部分。
話說,元件需要事件嗎?以前是事件驅動,現在是資料驅動,或者說是狀態驅動。以前監聽事件,現在只需要監聽狀態的變化即可,從dom脫離出來。
好吧,其實我基本已經不使用 emit 了,感覺似乎並不需要了。
1 Generic component enhancements - Discussion #436:
2 unplugin-vue-define-options - npm: https://www.npmjs.com/package/unplugin-vue-define-options
3 Announcing Vue 3.3 | The Vue Point: https://blog.vuejs.org/posts/vue-3-3
4 Vue 3.3 主要新特性詳解 - 三咲智子 Kevin Deng: https://xlog.sxzz.moe/vue-3-3
6 官方幫助檔案