雖然我知道 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 的美。