使用組合式API的TypeScript
此頁面假設您已經閱讀了使用TypeScript與Vue的概述。
元件屬性型別化
使用 <script setup>
當使用 <script setup> 時,defineProps() 宏支援根據其引數推斷props型別
vue
<script setup lang="ts">
const props = defineProps({
foo: { type: String, required: true },
bar: Number
})
props.foo // string
props.bar // number | undefined
</script>這被稱為“執行時宣告”,因為傳遞給 defineProps() 的引數將被用作執行時 props 選項。
但是,通常透過泛型型別引數定義props會更直接
vue
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>這被稱為“型別宣告”。編譯器將盡力根據型別引數推斷等價的執行時選項。在這種情況下,我們的第二個示例編譯成了與第一個示例完全相同的執行時選項。
您可以使用型別宣告或執行時宣告中的任何一個,但不能同時使用兩個。
我們還可以將props型別移動到單獨的介面中
vue
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>如果 Props 從外部源匯入,這也同樣適用。此功能要求TypeScript是Vue的依賴項。
vue
<script setup lang="ts">
import type { Props } from './foo'
const props = defineProps<Props>()
</script>語法限制
在版本3.2及以下版本中,defineProps()的泛型型別引數限制為型別字面或區域性介面的引用。
這個限制在3.3版本中已解決。Vue的最新版本支援在型別引數位置引用匯入的有限數量的複雜型別。然而,由於型別到執行時的轉換仍然是基於AST的,因此不支援需要實際型別分析的某些複雜型別,例如條件型別。您可以為單個prop使用條件型別,但不能為整個props物件使用。
屬性預設值
當使用基於型別的宣告時,我們失去了宣告屬性預設值的能力。可以透過使用響應式屬性解構 來解決這個問題。
ts
interface Props {
msg?: string
labels?: string[]
}
const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()在3.4及以下版本中,預設未啟用響應式屬性解構。一種替代方法是使用withDefaults編譯器宏
ts
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})這將編譯為等效的執行時屬性default選項。此外,withDefaults輔助函式提供了對預設值的型別檢查,並確保返回的props型別已移除具有預設值的屬性的必需標誌。
INFO
請注意,當使用withDefaults時,可變引用型別的預設值(如陣列或物件)應使用函式包裝,以避免意外修改和外部副作用。這確保每個元件例項都獲得自己的預設值副本。在使用解構時使用預設值時,這不是必要的。
沒有<script setup>
如果不使用<script setup>,則必須使用defineComponent()來啟用屬性型別推斷。傳遞給setup()的屬性物件型別從props選項推斷。
ts
import { defineComponent } from 'vue'
export default defineComponent({
props: {
message: String
},
setup(props) {
props.message // <-- type: string
}
})複雜屬性型別
使用基於型別的宣告時,屬性可以使用與任何其他型別類似的複雜型別
vue
<script setup lang="ts">
interface Book {
title: string
author: string
year: number
}
const props = defineProps<{
book: Book
}>()
</script>對於執行時宣告,我們可以使用PropType實用型別
ts
import type { PropType } from 'vue'
const props = defineProps({
book: Object as PropType<Book>
})如果我們直接指定props選項,它將以相同的方式工作
ts
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
export default defineComponent({
props: {
book: Object as PropType<Book>
}
})props選項更常與Options API一起使用,因此您將在TypeScript與Options API的指南中找到更多詳細示例。那些示例中顯示的技術也適用於使用defineProps()進行的執行時宣告。
為元件發射進行型別定義
在<script setup>中,也可以使用執行時宣告或型別宣告來對emit函式進行型別定義
vue
<script setup lang="ts">
// runtime
const emit = defineEmits(['change', 'update'])
// options based
const emit = defineEmits({
change: (id: number) => {
// return `true` or `false` to indicate
// validation pass / fail
},
update: (value: string) => {
// return `true` or `false` to indicate
// validation pass / fail
}
})
// type-based
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
// 3.3+: alternative, more succinct syntax
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>型別引數可以是以下之一
- 一個可呼叫函式型別,但寫成具有呼叫簽名的型別字面量。它將用作返回的
emit函式的型別。 - 一個型別字面量,其中鍵是事件名稱,值是表示事件額外接受引數的陣列/元組型別。上面的示例使用了命名元組,因此每個引數都可以有一個顯式的名稱。
正如我們所見,型別宣告使我們能夠對發射事件的型別約束有更細粒度的控制。
當不使用 <script setup> 時,defineComponent() 可以推斷 setup 上下文中暴露的 emit 函式允許的事件。
ts
import { defineComponent } from 'vue'
export default defineComponent({
emits: ['change'],
setup(props, { emit }) {
emit('change') // <-- type check / auto-completion
}
})輸入 ref()
引用會從初始值推斷型別。
ts
import { ref } from 'vue'
// inferred type: Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'有時我們可能需要為引用的內部值指定複雜型別。我們可以透過使用 Ref 型別來實現這一點。
ts
import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
year.value = 2020 // ok!或者,在呼叫 ref() 時傳遞泛型引數以覆蓋預設推斷。
ts
// resulting type: Ref<string | number>
const year = ref<string | number>('2020')
year.value = 2020 // ok!如果您指定了泛型型別引數但省略了初始值,則結果型別將是一個包含 undefined 的聯合型別。
ts
// inferred type: Ref<number | undefined>
const n = ref<number>()輸入 reactive()
reactive() 也會從其引數隱式推斷型別。
ts
import { reactive } from 'vue'
// inferred type: { title: string }
const book = reactive({ title: 'Vue 3 Guide' })要顯式型別化 reactive 屬性,我們可以使用介面。
ts
import { reactive } from 'vue'
interface Book {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue 3 Guide' })提示
不建議使用 reactive() 的泛型引數,因為返回的型別,它處理巢狀引用展開,與泛型引數型別不同。
輸入 computed()
computed() 根據獲取器的返回值推斷其型別。
ts
import { ref, computed } from 'vue'
const count = ref(0)
// inferred type: ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')您也可以透過泛型引數指定顯式型別。
ts
const double = computed<number>(() => {
// type error if this doesn't return a number
})輸入事件處理程式
處理原生 DOM 事件時,正確地型別化傳遞給處理程式的引數可能很有用。讓我們看看這個例子。
vue
<script setup lang="ts">
function handleChange(event) {
// `event` implicitly has `any` type
console.log(event.target.value)
}
</script>
<template>
<input type="text" @change="handleChange" />
</template>沒有型別註釋,event 引數將隱式具有 any 型別。如果使用 tsconfig.json 中的 "strict": true 或 "noImplicitAny": true,這將導致 TS 錯誤。因此,建議顯式註釋事件處理程式的引數。此外,您可能需要在使用 event 的屬性時使用型別斷言。
ts
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}輸入 Provide / Inject
提供和注入通常在單獨的元件中執行。為了正確型別化注入的值,Vue 提供了一個 InjectionKey 介面,它是一個擴充套件 Symbol 的泛型型別。它可以用來在提供者和消費者之間同步注入值的型別。
ts
import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'
const key = Symbol() as InjectionKey<string>
provide(key, 'foo') // providing non-string value will result in error
const foo = inject(key) // type of foo: string | undefined建議將注入金鑰放在單獨的檔案中,以便可以在多個元件中匯入。
當使用字串注入金鑰時,注入值的型別將是 unknown,需要透過泛型型別引數顯式宣告。
ts
const foo = inject<string>('foo') // type: string | undefined注意注入的值仍然可以是 undefined,因為沒有保證提供者會在執行時提供此值。
可以透過提供預設值來刪除 undefined 型別。
ts
const foo = inject<string>('foo', 'bar') // type: string如果您確定值始終被提供,也可以強制轉換值。
ts
const foo = inject('foo') as string輸入模板引用
使用 Vue 3.5 和 @vue/language-tools 2.1(為 IDE 語言服務和 vue-tsc 提供動力),在 SFC 中由 useTemplateRef() 建立的引用的型別可以根據匹配的 ref 屬性使用的元素自動推斷靜態引用。
在無法自動推斷的情況下,您仍然可以透過泛型引數將模板引用強制轉換為顯式型別。
ts
const el = useTemplateRef<HTMLInputElement>(null)3.5 版本之前的使用方法
模板引用應該使用顯式泛型型別引數和初始值 null 建立。
vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const el = ref<HTMLInputElement | null>(null)
onMounted(() => {
el.value?.focus()
})
</script>
<template>
<input ref="el" />
</template>要獲取正確的 DOM 介面,您可以檢查類似 MDN 的頁面。
請注意,為了確保嚴格的型別安全,在訪問 el.value 時需要使用可選鏈或型別守衛。這是因為直到元件掛載,初始引用值是 null,並且如果引用的元素透過 v-if 被解除安裝,它也可以被設定為 null。
元件模板引用的型別化
從 Vue 3.5 和 @vue/language-tools 2.1(為 IDE 語言服務和 vue-tsc 提供支援)開始,SFC 中使用 useTemplateRef() 建立的引用的型別可以根據匹配的 ref 屬性所使用的元素或元件自動推斷為靜態引用。
在無法自動推斷的情況下(例如,非 SFC 使用或動態元件),您仍然可以透過泛型引數將模板引用轉換為顯式型別。
為了獲取匯入元件的例項型別,我們首先需要透過 typeof 獲取其型別,然後使用 TypeScript 的內建 InstanceType 工具提取其例項型別。
vue
<!-- App.vue -->
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'
type FooType = InstanceType<typeof Foo>
type BarType = InstanceType<typeof Bar>
const compRef = useTemplateRef<FooType | BarType>('comp')
</script>
<template>
<component :is="Math.random() > 0.5 ? Foo : Bar" ref="comp" />
</template>在元件的確切型別不可用或不重要的情況下,可以使用 ComponentPublicInstance。這將只包含所有元件都共享的屬性,例如 $el。
ts
import { useTemplateRef } from 'vue'
import type { ComponentPublicInstance } from 'vue'
const child = useTemplateRef<ComponentPublicInstance | null>(null)當引用的元件是一個泛型元件時,例如 MyGenericModal。
vue
<!-- MyGenericModal.vue -->
<script setup lang="ts" generic="ContentType extends string | number">
import { ref } from 'vue'
const content = ref<ContentType | null>(null)
const open = (newContent: ContentType) => (content.value = newContent)
defineExpose({
open
})
</script>需要使用來自 vue-component-type-helpers 庫的 ComponentExposed 來引用,因為 InstanceType 無法使用。
vue
<!-- App.vue -->
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import MyGenericModal from './MyGenericModal.vue'
import type { ComponentExposed } from 'vue-component-type-helpers'
const modal = useTemplateRef<ComponentExposed<typeof MyGenericModal>>(null)
const openModal = () => {
modal.value?.open('newValue')
}
</script>請注意,從 @vue/language-tools 2.1+ 開始,靜態模板引用的型別可以自動推斷,上述內容僅在邊緣情況下需要。