雖然我知道 isDefined 很好用了,但然後呢?會用,但不知道原理就跟魔法一樣!這怎麼行!!程世社季子你一定要等我!等我學成歸來!!!
#挽回愛情的第四天
isDefined
是來自 VueUse 函式庫的一個實用工具函式,它可以協助我們在 Vue 中快速判斷變數是否定義 (可以參考昨天的文章)。在 Vue 開發中,我們經常會操作許多 ref
和 computed
資料,這些資料的值可能是空的、未定義的或是有效的值,透過 isDefined
,可以更方便的過濾掉不必要的空值,讓程式碼更為乾淨簡潔。
本文將詳細介紹 isDefined
函式的結構以及運用的小技巧。
我們先來看一下 isDefined
的完整程式碼:
import type { ComputedRef, Ref } from 'vue-demi'
// eslint-disable-next-line no-restricted-imports
import { unref } from 'vue-demi'
export function isDefined<T>(v: ComputedRef<T>): v is ComputedRef<Exclude<T, null | undefined>>
export function isDefined<T>(v: Ref<T>): v is Ref<Exclude<T, null | undefined>>
export function isDefined<T>(v: T): v is Exclude<T, null | undefined>
export function isDefined<T>(v: Ref<T>): boolean {
return unref(v) != null
}
unref
函式的使用這段程式碼中,最重要的部分是 unref(v)
。unref
是 Vue 的一個內建工具(一個可以用來打破 Vue2 與 Vue3 次元壁的強大工具),用來取得一個 ref
或 computed
的實際值。無論傳入的是 ref
或是一般的值,unref
都會幫助我們取得「真實的值」。這樣的設計,使得我們可以同時處理兩種情況,不需要額外檢查這個值是否是 Vue 的 ref
或是原生變數。
這段程式碼使用了三個不同的函式多載(Overloading)來應對不同類型的參數:
export function isDefined<T>(v: ComputedRef<T>): v is ComputedRef<Exclude<T, null | undefined>>
export function isDefined<T>(v: Ref<T>): v is Ref<Exclude<T, null | undefined>>
export function isDefined<T>(v: T): v is Exclude<T, null | undefined>
這些多載定義了 isDefined
可以接收不同類型的參數,包括 ComputedRef
、Ref
或是一般的變數 T
,並對它們進行型別推斷。
isDefined
接收一個 ComputedRef
,並檢查其內部的值是否已被定義(排除 null
或 undefined
)。如果值有效,TypeScript 將推斷這個值一定不是 null
或 undefined
,並返回對應的型別。isDefined
處理 Ref
類型的值,檢查其內部值是否被定義。這對於 Vue 的 ref
資料非常常見,能夠快速判斷它是否有意義的值。null
或 undefined
),並對這個值進行型別推斷。這些多載的函式定義讓 isDefined
具有靈活性,可以處理不同類型的資料結構,不僅限於 Vue 的 ref
和 computed
,也可以應用於普通變數。
最後的實作部分其實非常簡單:
export function isDefined<T>(v: Ref<T>): boolean {
return unref(v) != null
}
這段程式碼通過 unref
將傳入的值取出,然後與 null
進行比較。只要這個值不是 null
,isDefined
就會返回 true
,否則返回 false
。
在 TypeScript 中,類型收縮(Type Narrowing) 是指在程式運行過程中,TypeScript 根據條件判斷推斷出變數更精確的類型。這可以讓我們在編寫程式時獲得更好的型別提示和錯誤檢查,避免類型錯誤。這對於處理多種可能類型的變數特別有用。
例如,當你有一個變數可能是多種不同的類型(比如 string | undefined
),TypeScript 會自動進行類型收縮,當特定條件(如檢查是否為 undefined
)成立時,它會推斷該變數的類型變得更加具體。
使用 typeof
判斷(線上玩玩看這段程式碼)
TypeScript 會根據 typeof
的結果來收縮變數的類型:
function printId(id: string | number) {
if (typeof id === 'string') {
// 在這個區塊內,TypeScript 確定 id 是 string
console.log(id.toUpperCase())
} else {
// 在這個區塊內,TypeScript 確定 id 是 number
console.log(id.toFixed(2))
}
}
使用 instanceof
判斷 (線上玩玩看這段程式碼)
當使用 instanceof
判斷對象是否為某一類型時,TypeScript 會自動收縮類型:
class Dog {
bark() {
console.log('Woof!')
}
}
class Cat {
meow() {
console.log('Meow!')
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
// 這裡 TypeScript 知道 animal 是 Dog
animal.bark()
} else {
// 這裡 TypeScript 知道 animal 是 Cat
animal.meow()
}
}
使用相等檢查 (線上玩玩看這段程式碼)
當你使用 ===
或 !==
進行值的檢查時,TypeScript 會收縮類型:
function printValue(value: string | null) {
if (value !== null) {
// 這裡 TypeScript 知道 value 是 string
console.log(value)
}
}
自定義的類型守衛(Type Guards) (線上玩玩看這段程式碼)
你可以透過編寫自定義函式來告訴 TypeScript 一個變數在某種情況下應該具有什麼類型,這也是 isDefined
函式的核心原理。
function isString(value: any): value is string {
return typeof value === 'string'
}
function printValue(value: string | number) {
if (isString(value)) {
// 這裡 TypeScript 知道 value 是 string
console.log(value.toUpperCase())
} else {
// 這裡 TypeScript 知道 value 是 number
console.log(value.toFixed(2))
}
}
isDefined
中的類型收縮在 isDefined
函式中,類型收縮的作用是讓 TypeScript 確認一個值是否已定義(不是 null
或 undefined
)。這個函式的特點是,它在判斷值是否為 null
或 undefined
的同時,自動幫助 TypeScript 收縮變數的類型。
例如,以下範例中的 example
是一個可能是 string
或 undefined
的 Ref
:
const example = ref<string | undefined>(undefined)
if (isDefined(example)) {
// TypeScript 現在知道 example 是 Ref<string>,而不是 Ref<string | undefined>
console.log(example.value) // 可以安全地使用它
}
當 isDefined(example)
為 true
時,TypeScript 將 example
的類型收縮為 Ref<string>
,因此我們可以安全地使用 example.value
而不用擔心它會是 undefined
。這樣一來,我們不需要在每次取值時都做 null
或 undefined
的檢查。
有些讀者可能會疑惑:既然透過 TypeScript 的型別收縮就能達成目標,為何還要使用 VueUse 來解決?主要原因是它可以有效提高程式碼的可讀性(因為做得到)。在這方面,我推薦一本書:易讀程式之美學:提升程式碼可讀性的簡單法則。
isDefined
這個函式雖然簡單,卻巧妙地封裝了多個 TypeScript 技巧。這正是筆者選擇它作為首個介紹對象的原因。它沒有華麗的技巧,而是以簡潔的 TypeScript 封裝展現了 VueUse 的美。