EP56 - 組件API中使用TypeScript

閱讀時間約 22 分鐘
TypeScript with Composition API 老實說還是不太清楚好處
不過我知道每次開發的時候型別的錯誤,常常需要debug
組件API下的TypeScript有什麼特別嗎?
假設你已經閱讀過上篇有關使用 TypeScript 開發 Vue 的內容。

元件屬性類型標註 - Typing Component Props

使用 <script setup> - Using <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() 的參數將用作運行時屬性選項。

然而,通常更直接的方式是通過泛型類型參數定義純類型的屬性:

<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>

這稱為「基於類型的聲明」。編譯器會盡其所能根據類型參數推斷等效的運行時選項。在這種情況下,我們的第二個例子將編譯為與第一個例子完全相同的運行時選項。

您可以使用基於類型的聲明或運行時聲明,但不能同時使用兩者。

我們還可以將屬性類型移到單獨的接口中:

<script setup lang="ts">
interface Props {
foo: string
bar?: number
}

const props = defineProps<Props>()
</script>

如果 Props 是從外部來源導入的,這同樣有效。這個功能需要 TypeScript 作為 Vue 的對等依賴項。

<script setup lang="ts">
import type { Props } from './foo'

const props = defineProps<Props>()
</script>

語法限制 - Syntax Limitations

在 3.2 版本及以下,defineProps() 的泛型類型參數僅限於類型文字或本地接口的引用。

這個限制在 3.3 版本中已被解決。最新版本的 Vue 支持在類型參數位置引用導入的和一組有限的複雜類型。但是,由於類型到運行時轉換仍然是基於 AST 的,一些需要實際類型分析的複雜類型(例如條件類型)是不支持的。您可以對單個屬性的類型使用條件類型,但不能對整個屬性對象使用。

屬性默認值 - Props Default Values

使用基於類型的聲明時,我們失去了為屬性聲明默認值的能力。這可以通過使用反應式屬性解構來解決:

interface Props {
msg?: string
labels?: string[]
}

const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()

在 3.4 及以下版本中,反應式屬性解構不是默認啟用的。一個替代方案是使用 withDefaults 編譯器巨集:

interface Props {
msg?: string
labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})

這將編譯為等效的運行時屬性默認選項。此外,withDefaults 幫助器提供了默認值的類型檢查,並確保返回的屬性類型對於聲明了默認值的屬性移除了可選標誌。

注意 當使用 withDefaults 時,可變引用類型(如數組或對象)的默認值應該包裝在函數中,以避免意外修改和外部副作用。這確保每個組件實例獲得默認值的自己的副本。使用解構的默認值時不需要這樣做。

不使用 <script setup> - Without <script setup>

如果不使用 <script setup>,則需要使用 defineComponent() 來啟用屬性類型推斷。傳遞給 setup() 的屬性對象的類型是從屬性選項推斷的。

import { defineComponent } from 'vue'

export default defineComponent({
props: {
message: String
},
setup(props) {
props.message // <-- type: string
}
})

複雜屬性類型 - Complex prop types

使用基於類型的聲明時,屬性可以像任何其他類型一樣使用複雜類型:

<script setup lang="ts">
interface Book {
title: string
author: string
year: number
}

const props = defineProps<{
book: Book
}>()
</script>

對於運行時聲明,我們可以使用 PropType 實用類型:

import type { PropType } from 'vue'

const props = defineProps({
book: Object as PropType<Book>
})

如果我們直接指定屬性選項,這也以相同的方式工作:

import { defineComponent } from 'vue'
import type { PropType } from 'vue'

export default defineComponent({
props: {
book: Object as PropType<Book>
}
})

屬性選項更常用於選項 API,因此您會在 TypeScript 與選項 API 的指南中找到更多詳細的示例。這些示例中顯示的技術也適用於使用 defineProps() 的運行時聲明。

元件事件類型標註 - Typing Component Emits

<script setup> 中,emit 函數也可以使用運行時聲明或類型聲明來進行類型化:

<script setup lang="ts">
// 運行時
const emit = defineEmits(['change', 'update'])

// 選項方式
const emit = defineEmits({
change: (id: number) => {
// 返回 `true` 或 `false` 以指示驗證通過 / 失敗
},
update: (value: string) => {
// 返回 `true` 或 `false` 以指示驗證通過 / 失敗
}
})

// 類型聲明
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()

// 3.3+: 另一種更簡潔的語法
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>

類型參數可以是以下之一:

  1. 一個可調用的函數類型,但寫成帶有呼叫簽名的類型文本。它將用作返回的 emit 函數的類型。
  2. 一個鍵是事件名稱的類型文本,值是表示事件接受的附加參數的數組或元組類型。上面的示例使用了命名元組,使每個參數都有明確的名稱。

我們可以看到,類型聲明讓我們對發出的事件的類型約束有了更精細的控制。

當不使用 <script setup> 時,defineComponent() 可以推斷 setup 上下文中暴露的 emit 函數允許的事件:

import { defineComponent } from 'vue'

export default defineComponent({
emits: ['change'],
setup(props, { emit }) {
emit('change') // <-- 類型檢查 / 自動完成
}
})

ref() 型別化 - Typing ref()

ref 會從初始值推斷類型:

import { ref } from 'vue'

// 推斷類型:Ref<number>
const year = ref(2020)

// => TS 錯誤:類型 'string' 不能分配給類型 '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 // ok!

或者,在調用 ref() 時傳遞泛型參數以覆蓋默認推斷:

// 結果類型:Ref<string | number>
const year = ref<string | number>('2020')

year.value = 2020 // ok!

如果指定了泛型參數但省略了初始值,則結果類型將是包含 undefined 的聯合類型:

// 推斷類型:Ref<number | undefined>
const n = ref<number>()

reactive() 型別化 - Typing reactive()

reactive() 也會從其參數隱式推斷類型:

import { reactive } from 'vue'

// 推斷類型:{ title: string }
const book = reactive({ title: 'Vue 3 Guide' })

要顯式類型化一個響應式屬性,我們可以使用接口:

import { reactive } from 'vue'

interface Book {
title: string
year?: number
}

const book: Book = reactive({ title: 'Vue 3 Guide' })
提示:不建議使用 reactive() 的泛型參數,因為返回的類型(處理嵌套的 ref 解包)與泛型參數類型不同。

computed() 型別化 - Typing computed()

computed() 根據 getter 的返回值推斷其類型:

import { ref, computed } from 'vue'

const count = ref(0)

// 推斷類型:ComputedRef<number>
const double = computed(() => count.value * 2)

// => TS 錯誤:屬性 'split' 不存在於類型 'number' 上
const result = double.value.split('')

你也可以通過泛型參數指定顯式類型:

const double = computed<number>(() => {
// 類型錯誤如果這個不返回一個 number
})

為事件處理函數型別化 - Typing Event Handlers

在處理原生 DOM 事件時,將我們傳遞給處理程序的參數正確類型化可能會很有用。讓我們來看看這個例子:

<script setup lang="ts">
function 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 錯誤。因此,建議顯式註明事件處理程序的參數類型。此外,在訪問事件的屬性時,可能需要使用類型斷言:

function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}

為 Provide / Inject 型別化 - Typing 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

建議將注入鍵放在一個單獨的文件中,以便可以在多個組件中導入。

當使用字符串注入鍵時,注入值的類型將是未知的,需要通過泛型參數顯式聲明:

const foo = inject<string>('foo') // 類型:string | undefined

注意,注入的值仍然可以是 undefined,因為在運行時無法保證提供者會提供此值。

可以通過提供默認值來移除 undefined 類型:

const foo = inject<string>('foo', 'bar') // 類型:string

如果你確信該值始終會被提供,也可以強制轉換該值:

const foo = inject('foo') as string

為模板引用(Template Refs)型別化 - Typing Template Refs

從 Vue 3.5 和 @vue/language-tools 2.1 開始(為 IDE 語言服務和 vue-tsc 提供支持),SFC 中通過 useTemplateRef() 創建的引用類型可以根據 ref 屬性使用的元素自動推斷靜態引用的類型。

在無法自動推斷的情況下,仍然可以通過泛型參數將模板引用顯式類型化:

const el = useTemplateRef<HTMLInputElement>(null)

Usage before 3.5

模板引用應該使用明確的泛型參數和初始值 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>

為了獲得正確的 DOM 接口,可以查看 MDN 等頁面。

注意,為了嚴格的類型安全,在訪問 el.value 時需要使用可選鏈接或類型守衛。這是因為初始 ref 值為 null,直到組件被掛載,而且如果引用的元素被 v-if 卸載,也可能會設置為 null。

為組件模板引用(Component Template Refs)型別化 - Typing Component Template Refs​

在 Vue 3.5 和 @vue/language-tools 2.1 中(為 IDE 語言服務和 vue-tsc 提供支持),在單文件組件 (SFC) 中使用 useTemplateRef() 創建的引用類型,可以根據匹配的 ref 屬性使用的元素或組件自動推斷。

在無法自動推斷的情況下(例如非 SFC 使用或動態組件),您仍然可以通過泛型參數將模板引用強制轉換為明確的類型。

為了獲取導入組件的實例類型,我們需要首先通過 typeof 獲取其類型,然後使用 TypeScript 的內建 InstanceType 實用工具來提取其實例類型:

<!-- 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

import { useTemplateRef } from 'vue'
import type { ComponentPublicInstance } from 'vue'

const child = useTemplateRef<ComponentPublicInstance | null>(null)

在引用的組件是泛型組件的情況下,例如 MyGenericModal

<!-- 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 將不起作用。

<!-- 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+,靜態模板引用的類型可以自動推斷,以上情況僅在特殊情況下需要。

感覺TypeScript還很多要學習啊的www
對這個還不太熟~但好處蠻多~
avatar-img
2會員
70內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
使用 TypeScript 開發 Vue 的好處包括:靜態類型檢查可減少運行時錯誤,提升代碼重構的安全性。TypeScript 提供更好的開發體驗,因為 IDE 支持類型自動完成功能。
允許未經過濾的使用者內容執行會帶來攻擊風險。建議參考 HTML5 Security Cheat Sheet 和 OWASP's XSS Prevention Cheat Sheet,並檢查依賴項源代碼是否存在危險模式。
網站可及性(a11y)是指創建任何人都能使用的網站,無論是殘障人士、網速緩慢的用戶、使用過時或損壞硬體的人,還是處於不利環境中的人。例如,為視頻添加字幕能幫助聾人和聽力受損者,也能幫助處於嘈雜環境中的人。設計考慮應包括色彩對比、字體選擇、文本大小和語言。
頁面加載性能可透過PageSpeed Insights、WebPageTest等工具測量,而更新性能則需使用Chrome DevTools、Vue DevTools等進行分析。提升頁面加載性能的技術包括選擇合適架構(SSR或SSG)、(tree-shaking)、(code splitting)等。
在生產部署中,Vue 提供開發環境及生產環境的最佳實踐。開發階段有錯誤警告、驗證等功能,但生產環境須去除開發代碼以提升效能。未使用建構工具時,建議使用 .prod.js 生產版;使用工具如 Vite 或 Vue CLI 時,已預設生產配置,適合進行部署。
Server-Side Rendering (SSR) 是將 Vue.js 組件在伺服器上渲染為 HTML 字串,並直接發送到瀏覽器的一種技術。這樣可以提升頁面顯示速度,尤其在網速慢或設備性能差的情況下。SSR 應用的代碼可在伺服器和客戶端上共享,提高開發效率。
使用 TypeScript 開發 Vue 的好處包括:靜態類型檢查可減少運行時錯誤,提升代碼重構的安全性。TypeScript 提供更好的開發體驗,因為 IDE 支持類型自動完成功能。
允許未經過濾的使用者內容執行會帶來攻擊風險。建議參考 HTML5 Security Cheat Sheet 和 OWASP's XSS Prevention Cheat Sheet,並檢查依賴項源代碼是否存在危險模式。
網站可及性(a11y)是指創建任何人都能使用的網站,無論是殘障人士、網速緩慢的用戶、使用過時或損壞硬體的人,還是處於不利環境中的人。例如,為視頻添加字幕能幫助聾人和聽力受損者,也能幫助處於嘈雜環境中的人。設計考慮應包括色彩對比、字體選擇、文本大小和語言。
頁面加載性能可透過PageSpeed Insights、WebPageTest等工具測量,而更新性能則需使用Chrome DevTools、Vue DevTools等進行分析。提升頁面加載性能的技術包括選擇合適架構(SSR或SSG)、(tree-shaking)、(code splitting)等。
在生產部署中,Vue 提供開發環境及生產環境的最佳實踐。開發階段有錯誤警告、驗證等功能,但生產環境須去除開發代碼以提升效能。未使用建構工具時,建議使用 .prod.js 生產版;使用工具如 Vite 或 Vue CLI 時,已預設生產配置,適合進行部署。
Server-Side Rendering (SSR) 是將 Vue.js 組件在伺服器上渲染為 HTML 字串,並直接發送到瀏覽器的一種技術。這樣可以提升頁面顯示速度,尤其在網速慢或設備性能差的情況下。SSR 應用的代碼可在伺服器和客戶端上共享,提高開發效率。
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
如何在 Vite 專案中安裝和設置 TypeScript 及路徑別名的步驟,包括安裝必要的依賴、配置 vite.config.js、tsconfig.json 的設置,及如何創建類型聲明文件來正確識別 .vue 文件。
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
本章節的目的是介紹在TypeScript中如何進行例外處理。涵蓋了例外處理的重要性、語法、常見異常類型以及如何主動觸發異常訊息及用戶自定義異常訊息。為讀者提供了全面而深入的了解,以提高程式的可靠性、提供更好的反饋、增加程式的容錯性以及改善程式的可讀性。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
如何在 Vite 專案中安裝和設置 TypeScript 及路徑別名的步驟,包括安裝必要的依賴、配置 vite.config.js、tsconfig.json 的設置,及如何創建類型聲明文件來正確識別 .vue 文件。
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
本章節的目的是介紹在TypeScript中如何進行例外處理。涵蓋了例外處理的重要性、語法、常見異常類型以及如何主動觸發異常訊息及用戶自定義異常訊息。為讀者提供了全面而深入的了解,以提高程式的可靠性、提供更好的反饋、增加程式的容錯性以及改善程式的可讀性。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找