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