TypeScript with Options API, 上一篇組件API已經蠻頭大了
選項API雖然是舊版Vue的方法,但還是對整個框架的原理有幫助吧~
繼續來看看吧~
這篇文章假設您已經閱讀了《使用 TypeScript 的 Vue 概述》。
提示:雖然 Vue 支援使用 Options API 的 TypeScript,但建議透過 Composition API 使用 TypeScript,因為這樣可以提供更簡單、更高效和更穩健的類型推斷。
在 Options API 中,對屬性的類型推斷需要使用 defineComponent()
將元件包裝起來。這樣一來,Vue 就能根據屬性選項推斷屬性的類型,並考慮其他選項,例如 required: true
和 default
:
import { defineComponent } from 'vue'
export default defineComponent({
// 啟用類型推斷
props: {
name: String,
id: [Number, String],
msg: { type: String, required: true },
metadata: null
},
mounted() {
this.name // 類型:string | undefined
this.id // 類型:number | string | undefined
this.msg // 類型:string
this.metadata // 類型:any
}
})
然而,運行時的屬性選項僅支持使用構造函數作為屬性類型,無法指定複雜類型,例如具有嵌套屬性的物件或函數調用簽名。
要標註複雜的屬性類型,我們可以使用 PropType
實用類型:
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
export default defineComponent({
props: {
book: {
// 提供更具體的 `Object` 類型
type: Object as PropType<Book>,
required: true
},
// 也可以標註函數
callback: Function as PropType<(id: number) => void>
},
mounted() {
this.book.title // string
this.book.year // number
// TS 錯誤:類型 'string' 的參數
// 無法分配給類型 'number' 的參數
this.callback?.('123')
}
})
注意事項
如果您的 TypeScript 版本低於 4.7,當使用函數值作為驗證器和預設屬性選項時,必須小心,確保使用箭頭函數:
ts
複製程式碼import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
year?: number
}
export default defineComponent({
props: {
bookA: {
type: Object as PropType<Book>,
// 如果您的 TypeScript 版本低於 4.7,請確保使用箭頭函數
default: () => ({
title: '箭頭函數表達式'
}),
validator: (book: Book) => !!book.title
}
}
})
這樣可以防止 TypeScript 在這些函數內部推斷 this
的類型,這不幸地會導致類型推斷失敗。這是之前的設計限制,現在在 TypeScript 4.7 中已經有所改善。
我們可以使用 emits
選項的物件語法來宣告發射事件的預期有效載荷類型。此外,所有未聲明的發射事件在被呼叫時都會拋出類型錯誤:
import { defineComponent } from 'vue'
export default defineComponent({
emits: {
addBook(payload: { bookName: string }) {
// 執行運行時驗證
return payload.bookName.length > 0
}
},
methods: {
onSubmit() {
this.$emit('addBook', {
bookName: 123 // 類型錯誤!
})
this.$emit('non-declared-event') // 類型錯誤!
}
}
})
計算屬性會根據其返回值推斷其類型:
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
message: 'Hello!'
}
},
computed: {
greeting() {
return this.message + '!'
}
},
mounted() {
this.greeting // 類型: string
}
})
在某些情況下,您可能希望明確標註計算屬性的類型,以確保其實現是正確的:
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
message: 'Hello!'
}
},
computed: {
// 明確標註返回類型
greeting(): string {
return this.message + '!'
},
// 標註可寫的計算屬性
greetingUppercased: {
get(): string {
return this.greeting.toUpperCase()
},
set(newValue: string) {
this.message = newValue.toUpperCase()
}
}
}
})
在某些邊緣情況下,當 TypeScript 因循環推斷循環而無法推斷計算屬性的類型時,也可能需要明確的標註。
在處理原生 DOM 事件時,正確標註傳遞給處理程序的參數類型可能會很有用。讓我們看一下這個例子:
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
methods: {
handleChange(event) {
// `event` 隱式具有 `any` 類型
console.log(event.target.value)
}
}
})
</script>
<template>
<input type="text" @change="handleChange" />
</template>
如果沒有類型標註,事件參數將隱式具有 any
類型。如果在 tsconfig.json
中使用了 "strict": true
或 "noImplicitAny": true
,則會導致 TS 錯誤。因此,建議明確標註事件處理程序的參數。此外,當訪問事件的屬性時,您可能需要使用類型斷言:
import { defineComponent } from 'vue'
export default defineComponent({
methods: {
handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
}
})
一些插件通過 app.config.globalProperties
為所有組件實例安裝全域可用的屬性。例如,我們可以安裝 this.$http
用於數據獲取或 this.$translate
用於國際化。為了讓這與 TypeScript 相容,Vue 提供了 ComponentCustomProperties
接口,旨在通過 TypeScript 模組擴充來增強:
import axios from 'axios'
declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios
$translate: (key: string) => string
}
}
參考:
我們可以將這種類型擴充放在 .ts
檔案中,或者放在全專案的 *.d.ts
檔案中。無論哪種方式,請確保它包含在 tsconfig.json
中。對於庫或插件作者,這個檔案應在 package.json
的 types
屬性中指定。
為了利用模組擴充,您需要確保擴充放置在 TypeScript 模組中。也就是說,該檔案需要包含至少一個頂層的 import 或 export,即使它只是 export {}
。如果擴充放置在模組之外,它將覆蓋原始類型,而不是進行增強!
// 不工作,覆蓋原始類型。
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
// 正常工作
export {}
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
一些插件,例如 vue-router,提供對自訂組件選項的支持,例如 beforeRouteEnter
:
import { defineComponent } from 'vue'
export default defineComponent({
beforeRouteEnter(to, from, next) {
// ...
}
})
如果沒有適當的類型擴充,這個鉤子的參數將隱式具有 any
類型。我們可以擴充 ComponentCustomOptions
接口來支持這些自訂選項:
import { Route } from 'vue-router'
declare module 'vue' {
interface ComponentCustomOptions {
beforeRouteEnter?(to: Route, from: Route, next: () => void): void
}
}
現在 beforeRouteEnter
選項將被正確類型化。請注意,這只是一個示例,像 vue-router 這樣的良好類型化庫應該會自動在其自己的類型定義中執行這些擴充。
這種擴充的放置遵循與全域屬性擴充相同的限制。
參考:
在 TypeScript 中,組件 API(Component API)和項目 API(Project API)的差異主要在於它們的使用場景和型別定義方式。以下是一些關鍵的差異:
defineComponent()
時,TypeScript 能夠自動推斷組件的屬性類型。例如,對於 props 的定義,TypeScript 能夠從 props
選項中推斷型別。app.config.globalProperties
,以使其能與 TypeScript 進行協同工作。$http
或 $translate
屬性,並使用 declare module
來增強 Vue 的內部類型。defineComponent()
,而項目 API 通常需要使用 TypeScript 的模組擴充來增強 Vue 的全局類型。這使得開發者能夠在整個應用中使用這些自定義的全局屬性和方法。快來洗洗睡覺吧~
多接觸TypeScript好像慢慢理解~
他就是對各種東西都給他Type!的Script啊www