如何爲 Vue3 組件標註 TS 類型,看這個就夠了!
1 開篇
原文作者說是 2022 年也就是今年,Vue3 和 TS 是最熱門的前端技術,其實去年就已經很火了。
2 文章開始的地方
今天就給大家分享一下如何在 Vue3 組件中結合Composition-Api
使用 TS 類型。如果有不會或者不熟的小夥伴,一起學起來吧!
3 爲props
標註類型
使用 <script setup>
當使用<script setup>
時,defineProps()
宏函數支持從它的參數中推導類型:
<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
的類型,這種方式更加直接:
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
// or
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>
這被稱爲基於類型的聲明,編譯器會盡可能地嘗試根據類型參數推導出等價的運行時選項。這種方式的不足之處在於,失去了定義 props
默認值的能力。爲了解決這個問題,我們可以使用 withDefaults
編譯器宏:
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
上面代碼會被編譯爲等價的運行時 props
的 default
選項。
非 <script setup>
如果沒有使用 <script setup>
,那麼爲了開啓 props
的類型推導,必須使用 defineComponent()
。傳入 setup()
的 props
對象類型是從 props
選項中推導而來。
import { defineComponent } from 'vue'
export default defineComponent({
props: {
message: String
},
setup(props) {
props.message // <-- 類型:string
}
})
4 爲 emits 標註類型
使用 <script setup>
在 <script setup>
中,emit
函數的類型標註也可以使用 運行時聲明 或者 基於類型的聲明 :
<script setup lang="ts">
// 運行時
const emit = defineEmits(['change', 'update'])
// 基於類型
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
我們可以看到,基於類型的聲明 可以使我們對所觸發事件的類型進行更細粒度的控制。
非 <script setup>
若沒有使用 <script setup>
,defineComponent()
也可以根據 emits
選項推導暴露在 setup
上下文中的 emit
函數的類型:
import { defineComponent } from 'vue'
export default defineComponent({
emits: ['change'],
setup(props, { emit }) {
emit('change') // <-- 類型檢查 / 自動補全
}
})
5 爲 ref()
標註類型
默認推導類型
ref
會根據初始化時的值自動推導其類型:
import { ref } from 'vue'
// 推導出的類型:Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'
通過接口指定類型 有時我們可能想爲 ref 內的值指定一個更復雜的類型,可以使用 Ref 這個接口:
import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
year.value = 2020 // 成功!
通過泛型指定類型
或者,在調用 ref()
時傳入一個泛型參數,來覆蓋默認的推導行爲:
// 得到的類型:Ref<string | number>
const year = ref<string | number>('2020')
year.value = 2020 // 成功!
如果你指定了一個泛型參數但沒有給出初始值,那麼最後得到的就將是一個包含 undefined
的聯合類型:
// 推導得到的類型:Ref<number | undefined>
const n = ref<number>()
6 爲 reactive()
標註類型
默認推導類型
reactive()
也會隱式地從它的參數中推導類型:
import { reactive } from 'vue'
// 推導得到的類型:{ title: string }
const book = reactive({ title: 'Vue 3 指引' })
通過接口指定類型
要顯式地指定一個 reactive
變量的類型,我們可以使用接口:
import { reactive } from 'vue'
interface Book {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue 3 指引' })
7 爲 computed()
標註類型
默認推導類型
computed()
會自動從其計算函數的返回值上推導出類型:
import { ref, computed } from 'vue'
const count = ref(0)
// 推導得到的類型:ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')
通過泛型指定類型
你還可以通過泛型參數顯式指定類型:
const double = computed<number>(() => {
// 若返回值不是 number 類型則會報錯
})
8 爲事件處理函數標註類型
在處理原生 DOM 事件時,應該給事件處理函數的參數正確地標註類型。讓我們看一下這個例子:
<script setup lang="ts">
function handleChange(event) {
// `event` 隱式地標註爲 `any` 類型
console.log(event.target.value)
}
</script>
<template>
<input type="text" @change="handleChange" />
</template>
沒有類型標註時,這個 event
參數會隱式地標註爲 any 類型。這也會在 tsconfig.json
中配置了 "strict": true
或 "noImplicitAny": true
時報出一個 TS 錯誤。因此,建議顯式地爲事件處理函數的參數標註類型。此外,你可能需要顯式地強制轉換 event
上的屬性:
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
9 爲 provide / inject
標註類型
provide
和 inject
通常會在不同的組件中運行。要正確地爲注入的值標記類型,Vue 提供了一個 InjectionKey
接口,它是一個繼承自 Symbol
的泛型類型,可以用來在提供者和消費者之間同步注入值的類型:
import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'
const key = Symbol() as InjectionKey<string>
provide(key, 'foo') // 若提供的是非字符串值會導致錯誤
const foo = inject(key) // foo 的類型:string | undefined
建議將注入 key 的類型放在一個單獨的文件中,這樣它就可以被多個組件導入。當使用字符串注入 key 時,注入值的類型是 unknown
,需要通過泛型參數顯式聲明:
const foo = inject<string>('key') // 類型:string | undefined
注意注入的值仍然可以是 undefined
,因爲無法保證提供者一定會在運行時 provide
這個值。當提供了一個默認值後,這個 undefined
類型就可以被移除:
const foo = inject<string>('foo', 'bar') // 類型:string
如果你確定該值將始終被提供,則還可以強制轉換該值:
const foo = inject('foo') as string
10 爲 dom
模板引用標註類型
模板 ref
需要通過一個顯式指定的泛型參數和一個初始值 null
來創建:
<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>
注意爲了嚴格的類型安全,有必要在訪問 el.value
時使用可選鏈或類型守衛。這是因爲直到組件被掛載前,這個 ref 的值都是初始的 null
,並且 v-if
將引用的元素卸載時也會被設置爲 null
。
11 爲組件模板引用標註類型
有時,我們需要爲一個子組件添加一個模板 ref,以便調用它公開的方法。比如,我們有一個 MyModal
子組件,它有一個打開模態框的方法:
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
defineExpose({
open
})
</script>
爲了獲取 MyModal
的類型,我們首先需要通過 typeof
得到其類型,再使用 TypeScript
內置的InstanceType
工具類型來獲取其實例類型:
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const modal = ref<InstanceType<typeof MyModal> | null>(null)
const openModal = () => {
modal.value?.open()
}
</script>
作者:前端阿飛
來源:https://juejin.cn/post/7129130323148800031
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Fpq8SOgRutbLpGGr6OqBlw