雖然 3.3 當前還處於 beta 階段但是其帶來的一些特性十分激動人心,就在這裡簡單的給大家帶來新特性的前瞻,為以後的升級簡單做準備😁
泛型組件支持#
Vue一直以來都是沒辦法很好的實現泛型組件,終於在 3.3 版本增加了這一功能
首先是面向 TSX 用戶為defineComponent
工具函數增加了泛型支持,當參數傳入一個泛型函數時類型會提示正常,比如我們可以基於這個特性使用 tsx 簡單構造一個表格組件
import { defineComponent } from 'vue';
type Props<T> = { data: T[]; columns: { label: string; field: keyof T }[] };
const Table = defineComponent(<T,>(props: Props<T>) => {
return () => (
<table>
<thead>
<tr>
{props.columns?.map((item) => (
<th>{item.label}</th>
))}
</tr>
</thead>
<tbody>
{props.data?.map((row) =>
props.columns?.map((column) => <td>{row[column.field]}</td>)
)}
</tbody>
</table>
);
});
export default Object.assign(Table, {
name: 'GenericsTableTsx',
props: ['columns', 'data']
});
但是值得注意的是我們仍需要為這個組件傳入props
屬性,否則在使用的時候會將應該是props
的的屬性掛載到$attrs
上,這點其實基本上杜絕了這樣的用法,所以說僅僅只是類型正確,不太推薦生產用這樣的方法構建泛型組件。
SFC 泛型組件支持#
其實上面的功能還是為了鋪墊這個,我們了解怎麼用 SFC 來復現上面的組件
<template>
<table>
<thead>
<tr>
<th v-for="item in columns">
<slot name="header-cell" v-bind="{ label: item.label }">
{{ item.label }}
</slot>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data">
<td v-for="column in columns">
<slot name="cell" v-bind="{ data: row[column.field] }">
{{ row[column.field] }}
</slot>
</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts" generic="T">
const { columns, data } = defineProps<{
data: T[];
columns: {
label: string;
field: keyof T;
}[];
}>();
</script>
[email protected]
為script
增加了一个属性generic
用于创建泛型参数,多个参数当然也像是 ts 中使用,
隔开。
評價:很強的新特性,vue 終於有泛型組件了真的是可喜可賀,就是對於 TSX 的支持還是需要額外增加 props 屬性比較麻煩,這個問題也是比較久遠的了,希望 vue 團隊以後在為 TSX 的開發體驗提升上努努力
defineProps 宏支持引入的類型#
這個需求已經 2 年過去了,不過大部分開發者都有使用一些社區插件來達到這個用法,現在官方終於提供了,在 3.3 我們可以輕鬆的使用外部導入的類型創建Props
<script setup lang="ts">
import type { SomeType } from 'some-where'
const props = defineProps<{ data: SomeType }>()
</script>
評價:眾望所歸,更方便的管理在 Vue 項目中的類型,不需要再在 SFC 中寫又臭又長的類型體操了
defineEmits 宏更簡便的寫法#
對於 3.2,defineEmits
基於泛型需要這樣使用
defineEmits<{
(e: 'foo', id: string): void
(e: 'bar',...args: any[]): void
}>()
3.3 的寫法,對於單個參數使用具名元組的方式定義,如果使用rest params
的參數可以直接使用T[]
來解決
defineEmits<{
foo: [id: string]
bar: any[]
}>()
評價:提升 DX 的小功能,函數重載的形式寫起太多的 emits 確實有點煩人
為 v-model 帶來新的工具#
這是來自智子君的新特性,可以在<script setup/>
中使用defineModel
和非<script setup/>
中使用的useModel
工具
// 默認的model (通過 `v-model`)
const modelValue = defineModel()
// ^? Ref<any>
modelValue.value = 10
const modelValue = defineModel<string>() //增加類型
// ^? Ref<string | undefined>
modelValue.value = "hello"
// 帶有設置的默認model, 要求非undefined
const modelValue = defineModel<string>({ required: true })
// ^? Ref<string>
// 特定名稱的model (通過 `v-model:count` )
const count = defineModel<number>('count')
count.value++
// 具有默認值的特定名稱的model
const count = defineModel<number>('count', { default: 0 })
// ^? Ref<number>
// 本地作用域可變的 model, 顧名思義
// 可以不需要父組件傳遞v-model
const count = defineModel<number>('count', { local: true, default: 0 })
還有useModel
作為非<script setup/>
中使用的工具
import { useModel } from 'vue'
export default {
props: {
modelValue: { type: Number, default: 0 },
},
emits: ['update:modelValue'],
setup(props) {
const modelValue = useModel(props, 'modelValue')
// ^? Ref<number>
return { modelValue }
}
}
評價:又一提升 DX 的利器,定義一個
v-model
的屬性確實比較繁瑣,而且在 sfc 內實用性不強,一般需要搭配vueuse/useVModels
使用,官方加入這個宏和工具函數確實是很不錯
defineOptions#
又是智子君的 pr,早前來自RFC,這個內容的話應該不少人都在Vue Macro
中用過了
本來 Vue 如果你需要在<script setup>
中定義一些原先Option Api
的屬性比如inheritAttrs/name
是需要創建一個<script>
單獨導出這兩個屬性的,現在有了defineOptions
就可以省去這一步驟
<script setup>
// 一些代碼
</script>
<script>
export default {
name: "ComponentName"
}
</script>
<script setup>
defineOptions({
name: "ComponentName"
})
// 一些代碼
</script>
評價:這個特性可以在
Vue Macro
使用到,先行體驗,反正我是用上了很爽
defineSlots 宏以及 slots 屬性#
還是來自智子君,TQL
允許定義slots
的具體類型,首先是新增了一個SlotsType
以及slots
屬性可以options api
中使用
import { SlotsType } from 'vue'
export default defineComponent({
slots: Object as SlotsType<{
default: { foo: string; bar: number }
item: { data: number }
}>,
setup(props, { slots }) {
expectType<undefined | ((scope: { foo: string; bar: number }) => any)>(
slots.default
)
expectType<undefined | ((scope: { data: number }) => any)>(slots.item)
}
})
對於這個定義的組件SlotComponent
,再組件中使用的話就是
<template>
<SlotComponent>
<template #default="{ foo, bar }">
{{ foo }} is string,{{ bar }} is number
</template>
<template #item="{ data }">
{{ data }} is number
</template>
</SlotComponent>
</template>
defineSlots
和slots
屬性類似,不過提供一個函數語法
// 與 對象語法表現一致,謝謝ES沒有將default當屬性關鍵詞 可喜可賀可喜可賀😆
const slots = defineSlots<{
default(props: { foo: string; bar: number }): any // or VNode[]
}>()
評價: slot 有正確的類型的話對於組件庫來說是一個挺好的消息,畢竟從引入
scopeSlot
到現在用戶都不能很好地確定自己該怎麼用某個scopeSlot
模板中使用 console.log#
突然的調試可能會用到的console.log
但是在模板中不好使,現在 3.3 加上了額外支持,不需要再自己為模板作用域增加一個函數來打印東西了
評價:無傷大雅,提升 DX,偶爾會用到會感覺很舒服
不太重要的特性#
對 Suspense 的改進#
個人覺得 vue 的<Suspense>
可以暫時不用關注,實驗性特性好久了,pr 在這裡
廢棄和修改的特性#
v-is 指令廢棄#
廢棄v-is
了,全部改用:is
指令(好奇真的有人還在用這個指令嗎?)
app.config.unwrapInjectedRef#
app.config.unwrapInjectedRef
這個屬性沒了,在 3.3 會默認對使用Option api
的inject
屬性進行注入的 ref 進行拆包,
vnode hook 廢棄#
vnode hook 應該用戶比較少,就是有一個@vnode-*
的指令變成了@vue:*
,這個特性應該蠻少用的,甚至連網上都沒有什麼介紹,好像是為 Vnode 的生命週期提供一些類似與組件生命週期的功能,不知道有沒有清楚這個特性能幹嗎的夥伴介紹下。
對於生態開發者的改進#
app.runWithContext()#
在 App 上增加了一个runWithContext()
可以用于确保对应用程序级别的全局变量存在,比如通过provide
的各个值,可以用于改进 vue 生态的各个包,pinia/vue-router
之类的
const app = createApp(App)
app.provide('foo', 1)
app.runWithContext(() => inject('foo')) // should return 1
hasInjectionContext#
hasInjectionContext
這是面向基於 vue 的庫作者用於檢查是否可以使用inject()
的工具,如果當前環境可以使用就返回 true,不可以的環境其實就是 setup 外了,庫作者使用該函數可以省去額外對當前環境的檢測。
評價:這些對生態開發者來說比較有用的功能,如果有寫庫的小夥伴可以注意一下
需要注意的#
對於 TSX 用戶,vue3.3 不在默認註冊全局 JSX 命名空間,需要手動在 tsconfig.json 中修改jsxImportSource或者使用魔法註釋/* @jsxImportSource vue */
這是避免全局 jsx 類型衝突。