代號:Rurouni Kenshin,vue3.3正式發(fā)布,快來嘗鮮!?。?/h1>
1寫在前面
Vue官方團(tuán)隊(duì)在5月10日宣布發(fā)布 Vue 3.3 "浪客劍心"!
此版本專注于改進(jìn)開發(fā)人員體驗(yàn) ,特別是 SFC <script setup> 與 TypeScript 的使用。一同發(fā)布的還有 1.6 版本的 Vue Language Tools (以前稱為 Volar),我們解決了很多 Vue 與 TypeScript 使用上的痛點(diǎn)。
想要體驗(yàn)Vue3.3的前提是:升級Vue到最新版本,同時(shí)升級對應(yīng)的依賴項(xiàng),對于一些實(shí)驗(yàn)性功能需要進(jìn)行手動(dòng)開啟。
依賴更新
升級到 3.3 時(shí),建議同時(shí)更新以下依賴項(xiàng):
volar / vue-tsc@^1.6.4
vite@^4.3.5
@vitejs/plugin-vue@^4.2.0
vue-loader@^17.1.0(如果使用 webpack 或 vue-cli)
更多更新日志可以查看:https://github.com/vuejs/core/blob/main/CHANGELOG.md#330-2023-05-08
2defineProps和defineEmits支持外部import類型和復(fù)雜類型
在vue3.3之前的vue文件中, defineProps和defineEmits僅支持使用當(dāng)前文件定義的類型,不能通過import導(dǎo)入外部定義的類型,且僅支持類型字面量和當(dāng)前文件的interface。
在vue3.3對這一限制進(jìn)行了優(yōu)化,編譯器可以通過AST支持解析外部impoort的類型,并支持有限的復(fù)雜類型。
<script lang='ts' setup>
import type { TestProps } from "./props";
// vue3.3 支持外部導(dǎo)入props類型,通過泛型定義props上的屬性類型
defineProps<TestProps>();
defineProps<TestProps & { age: number }>();
</script>
注意:defineProps和defineEmits并不是100%全面支持復(fù)雜類型,例如不能對整個(gè)props對象像普通類型一樣進(jìn)行條件類型,但是可以對單個(gè)prop類型使用條件類型。
import type { TestProps } from "./props";
defineProps<TestProps extends Object ? TestProps : {}>();
如上面使用條件類型,將會拋出異常:
詳細(xì)說明見:PR#8083
3通過generic屬性定義泛型組件
使用vue的setup語法糖的組件,可以通過在generic屬性傳遞泛型類型參數(shù),從而構(gòu)成泛型組件:
<script lang='ts' setup generic="T, U">
defineProps<{
options: T[],
selected: T,
id: U
}>()
</script>
通過generic接受的泛型類型,和Typescript中的泛型的參數(shù)列表使用方法是一樣的,這就意味著你可以定義多個(gè)泛型參數(shù)、使用extends約束、默認(rèn)類型和引用import導(dǎo)入的類型。
<script lang='ts' setup generic="T extends Option, U">
import { Option } from "./props"
defineProps<{
options: T[],
selected: T,
id: U
}>()
</script>
注意:此功能在之前需要顯式開啟,在最新版本的volar/vue-tsc中已支持默認(rèn)開啟。
詳細(xì)說明見:RFCS#436
4更符合人體工程學(xué)的 defineEmits
在Vue3.3之前的defineEmits的類型僅支持使用簽名語法:
interface EmitsType<T, U>{
(event: 'change', value: T): void
(event: 'click', value: T, ...rest:U[]): void
}
defineEmits<EmitsType<string, any>>()
interface EmitsType<T, U>{
change: (value: T) => void
click: (value: T, ...rest:U[]) => void
}
defineEmits<EmitsType<string, any>>()
此種方式定義的類型和emit返回的類型匹配,但是在實(shí)際開發(fā)中編寫代碼比較冗長和笨拙,Vue3.3對此進(jìn)行優(yōu)化使得更加符合人類習(xí)慣。
interface EmitsType<T, U>{
change: [ value: T ],
click: [ value:T, ...rest: U[] ]
}
defineEmits<EmitsType<string,any>>()
在使用類型字面量形式中,鍵key是事件名稱,值value是指定附加參數(shù)的數(shù)組類型,對此可以使用標(biāo)識元組元素的形式。見元組元素標(biāo)記
Vue3.3對于defineEmits的泛型定義形式并不是破壞性改變,對原有的簽名語法依舊支持。
5使用 defineSlots 設(shè)置 slots 類型
Vue3.3后的defineSlots宏支持聲明預(yù)期的插槽和對應(yīng)插槽的props類型。
<template>
<div>
<slot title="default" />
<slot name="header" title="header"></slot>
</div>
</template>
<script lang='ts' setup>
interface SlotsType{
default?: (props: {
title: string
})=>any;
header?: (props: {
title: string
})=>any
}
defineSlots<SlotsType>()
</script>
當(dāng)前,defineSlots僅接受一個(gè)類型參數(shù),不支持運(yùn)行時(shí)參數(shù),且類型參數(shù)限制為一個(gè)類型字面量:
- key:slot名稱
- value:slot函數(shù),插槽函數(shù)的第一個(gè)參數(shù)是插槽期望接受的props,其定義的類型是用于模板中的插槽props。
defineSlots 的返回值與 useSlots 返回的插槽對象相同。
注意:
- volar / vue-tsc 尚未實(shí)現(xiàn) slots 類型檢查。
- slot 函數(shù)的返回類型目前是忽略的,是any類型,但我們可能會在將來利用它進(jìn)行 slot 內(nèi)容檢查。
此外,defineComponent 用法也有對應(yīng)的 slots 選項(xiàng)。 這兩個(gè) API 都沒有運(yùn)行時(shí)影響,并且純粹用作 IDE 和 vue-tsc 的類型提示。
import { SlotsType } from 'vue'
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)
}
})
更多詳情見:PR#7982
6實(shí)驗(yàn)性功能
響應(yīng)式props解構(gòu)
響應(yīng)式props解構(gòu)此前是現(xiàn)已刪除的 Reactivity Transform 的一部分,Vue3.3現(xiàn)已將其拆分成獨(dú)立功能。
響應(yīng)式props解構(gòu)的功能毫無疑問是為解構(gòu)后的props屬性提供響應(yīng)式,此外還提供了更加符合用戶習(xí)慣的聲明props默認(rèn)值方式。
<template>
<div>
my friend‘s name is: {{ name }}
</div>
</template>
<script lang='ts' setup>
import { watchEffect } from "vue"
// 官方給出的demo是這樣寫,但是我發(fā)現(xiàn)不能對props進(jìn)行泛型類型聲明,解構(gòu)后的屬性失去了類型推斷 不建議使用這種方式
const { name ="dudu" } = defineProps(["name"])
// 個(gè)人推薦搭配withDefaults進(jìn)行賦默認(rèn)值和響應(yīng)式解構(gòu),有解構(gòu)需要時(shí)可以進(jìn)行解構(gòu)賦值,沒有時(shí)可以在withDefaults中賦默認(rèn)值
const { name = "dudu" } = withDefaults(defineProps<{
}>(),{})
watchEffect(()=>{
// 在 watchers 和 computed getters 中訪問 `name`
// 將其作為依賴項(xiàng)進(jìn)行跟蹤,就像訪問 `name` 一樣
console.log(`my friend‘s name is: ${name}`)
})
</script>
官方給出的demo是這樣寫,但是實(shí)際使用中發(fā)現(xiàn)不能對props進(jìn)行泛型類型聲明,解構(gòu)后的屬性失去了類型推斷 不建議使用這種方式。
const { name ="dudu" } = defineProps(["name"])
個(gè)人推薦搭配withDefaults進(jìn)行賦默認(rèn)值和響應(yīng)式解構(gòu),有解構(gòu)需要時(shí)可以進(jìn)行解構(gòu)賦值,沒有時(shí)可以在withDefaults中賦默認(rèn)值。
const { name = "dudu" } = withDefaults(defineProps<{
}>(),{})
如果搭配withDefaults同時(shí)進(jìn)行響應(yīng)式解構(gòu)時(shí)賦默認(rèn)值,那么解構(gòu)時(shí)不能改變withDefaults賦的默認(rèn)值,也就是withDefaults的默認(rèn)值是不可改變的。
const { name ="dudu" } = withDefaults(defineProps<{
name: string
}>(),{
name:"dudududududu"
})
我們看到只會顯示withDefaults的默認(rèn)值:
注意:此功能是實(shí)驗(yàn)性功能,需要在打包配置中進(jìn)行手動(dòng)開啟。
更多詳情見:RFC#502
defineModel
在Vue3.3前的組件想要支持v-model使用,需要:
- 聲明props
- 在打算更新props時(shí),使用emit進(jìn)行相應(yīng)的update:proName事件
子組件支持v-model的方式:
<template>
<input :value="modelValue" @input="onInput" />
</template>
<script lang='ts' setup>
// BEFORE: Vue3.3前的方式
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
console.log(props.modelValue)
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
與此同時(shí),父組件需要進(jìn)行對應(yīng)聲明使用:
<script setup lang="ts">
import DefineModel from './components/DefineModel.vue';
const name = ref("dudu")
</script>
<template>
<DefineModel v-model:modelValue="name"/>
</template>
執(zhí)行效果:
Vue3.3引入了一個(gè)名為 defineModel 的新 SFC 宏,該宏增強(qiáng)了聲明 v-model 使用的雙向綁定道具時(shí)的開發(fā)人員體驗(yàn)。使用 defineModel ,v-model綁定的props 可以像 ref 一樣被聲明和解構(gòu)。
- defineModel 宏是 <script setup> 的專用功能。
- 編譯時(shí),它將聲明一個(gè)同名的 prop 和一個(gè)相應(yīng)的 update:propName 事件。
- 默認(rèn)情況下處于禁用狀態(tài),需要手動(dòng)開啟。
defineModel簡化了聲明props和emit的過程,會自動(dòng)注冊一個(gè)prop,并返回一個(gè)可以直接改變的 ref:
<template>
<div>輸入的名字:{{modelValue}}</div>
<input v-model="modelValue" />
</template>
<script lang='ts' setup>
// AFTER: Vue3.3后的方式
const modelValue = defineModel()
console.log(modelValue.value)
</script>
根據(jù)接受 defineModel 返回值的變量名,這里是 modelValue,會自動(dòng)定義 props 名為 modelValue,emit 事件為 update:modelValue。
此外,defineModel也支持聲明變量名稱、類型和賦初值,其實(shí)就是props和emit的結(jié)合體。
const count = defineModel<number>('count', { default: 0 })
注意:Vue3.3引入了一個(gè)新的編譯器腳本選項(xiàng) defineModel ,默認(rèn)情況下處于禁用狀態(tài),需要手動(dòng)進(jìn)行配置開啟。
在vite的配置如下:
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue({
// propsDestructure: true,
script: {
defineModel: true,
}
})],
})
更多詳情見:RFC#503
7其他特性
新增宏defineOptions
新增defineOptions 允許直接在 <script setup> 中聲明組件選項(xiàng),而無需單獨(dú)的 <script> 塊:
<script setup>
defineOptions({ inheritAttrs: false })
</script>
增強(qiáng) toRef 和 toValue 實(shí)現(xiàn)更好的 getter 支持
toRef 已得到增強(qiáng)以支持將值/getters/現(xiàn)有 refs 規(guī)范化為 refs:
// 相當(dāng)于 ref(1)
toRef(1)
// 創(chuàng)建只讀 ref,使用 .value 時(shí)執(zhí)行 getter
toRef(() => props.foo)
// 返回 ref
toRef(existingRef)
使用 getter 調(diào)用 toRef 類似于 computed,但是當(dāng) getter 只執(zhí)行屬性訪問而沒有昂貴的計(jì)算時(shí),可以更高效。
新的 toValue 工具方法提供相反的功能,即將值/ getter / ref 規(guī)范化為值:
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
toValue 可以在組合式函數(shù)中代替 unref,以便組合式函數(shù)可以接受 getter 作為響應(yīng)式數(shù)據(jù)源:
// 以前:分配不必要的中間引用
useFeature(computed(() => props.foo))
useFeature(toRef(props, 'foo'))
// 現(xiàn)在:更高效和簡潔
useFeature(() => props.foo)
toRef 和 toValue 之間的關(guān)系類似于 ref 和 unref 之間的關(guān)系,主要區(qū)別在于 getter 函數(shù)的特殊處理。
JSX 導(dǎo)入源支持
目前,Vue 的類型自動(dòng)注冊全局 JSX 類型。這可能會與需要 JSX 類型推斷的其他庫一起使用時(shí)發(fā)生沖突,特別是 React。
從3.3開始,Vue 支持通過 TypeScript 的 jsxImportSource 選項(xiàng)指定 JSX 命名空間。這允許用戶根據(jù)其需要,選擇全局或每個(gè)文件的選擇加入。
為了向后兼容,3.3 仍然全局注冊 JSX 命名空間。我們計(jì)劃在 3.4 中刪除默認(rèn)的全局注冊。如果您正在使用 TSX 與 Vue,請?jiān)谏壍?3.3后在 tsconfig.json 中添加顯式的 jsxImportSource,以避免在 3.4 中出現(xiàn)問題。
按計(jì)劃,Vue官方的目標(biāo)是在2023年開始制作較小,更頻繁的功能發(fā)行版。
更多內(nèi)容閱讀:vue官方博客https://blog.vuejs.org/posts/vue-3-3
8寫在最后
Vue3.3沒有進(jìn)行大的功能破壞性改變,在使用體驗(yàn)上更加符合人體工程學(xué)和用戶習(xí)慣,沒有心智負(fù)擔(dān)。
Vue3.3主要有以下變化:
- 增強(qiáng)defineProps和defineEmits支持外部import類型和復(fù)雜類型
- 新增通過generic屬性定義泛型組件
- 增強(qiáng)更符合人體工程學(xué)的 defineEmits
- 增強(qiáng)使用 defineSlots 設(shè)置 slots 類型
實(shí)驗(yàn)性功能:
- 響應(yīng)式props解構(gòu)
- 新增defineModel實(shí)現(xiàn)v-model屬性更新值
其他特性:
- 新增宏defineOptions聲明組件選項(xiàng)
- 增強(qiáng) toRef 和 toValue 實(shí)現(xiàn)更好的 getter 支持
- JSX 導(dǎo)入源支持
學(xué)而知不足,水平有限,還望諸君多多指教。覺得文章不錯(cuò)的讀者,不妨點(diǎn)個(gè)關(guān)注,收藏起來上班摸魚的時(shí)候品嘗。