iT邦幫忙

2024 iThome 鐵人賽

DAY 4
1
JavaScript

不會 VueUse 而被提分手的我系列 第 4

D-04 isDefined 解析與動機 — 簡單感受一下 VueUse 的美 2

  • 分享至 

  • xImage
  •  

雖然我知道 isDefined 很好用了,但然後呢?會用,但不知道原理就跟魔法一樣!這怎麼行!!程世社季子你一定要等我!等我學成歸來!!!
#挽回愛情的第四天
D-04 isDefined 解析與動機 — 簡單感受一下 VueUse 的美 2

文件連結
https://ithelp.ithome.com.tw/upload/images/20240917/20162115bk4Pe3XvQc.png

原始碼連結
https://ithelp.ithome.com.tw/upload/images/20240917/20162115aXrSe0GLbQ.png

isDefined 是來自 VueUse 函式庫的一個實用工具函式,它可以協助我們在 Vue 中快速判斷變數是否定義 (可以參考昨天的文章)。在 Vue 開發中,我們經常會操作許多 refcomputed 資料,這些資料的值可能是空的、未定義的或是有效的值,透過 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
}

1. unref 函式的使用

這段程式碼中,最重要的部分是 unref(v)unref 是 Vue 的一個內建工具(一個可以用來打破 Vue2 與 Vue3 次元壁的強大工具),用來取得一個 refcomputed 的實際值。無論傳入的是 ref 或是一般的值,unref 都會幫助我們取得「真實的值」。這樣的設計,使得我們可以同時處理兩種情況,不需要額外檢查這個值是否是 Vue 的 ref 或是原生變數。


2. 函式多載(Overloading)

這段程式碼使用了三個不同的函式多載(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 可以接收不同類型的參數,包括 ComputedRefRef 或是一般的變數 T,並對它們進行型別推斷。

  1. 第一個多載
    • isDefined 接收一個 ComputedRef,並檢查其內部的值是否已被定義(排除 nullundefined)。如果值有效,TypeScript 將推斷這個值一定不是 nullundefined,並返回對應的型別。
  2. 第二個多載
    • isDefined 處理 Ref 類型的值,檢查其內部值是否被定義。這對於 Vue 的 ref 資料非常常見,能夠快速判斷它是否有意義的值。
  3. 第三個多載
    • 當傳入普通變數時,它會檢查這個變數本身是否為已定義的值(排除 nullundefined),並對這個值進行型別推斷。

這些多載的函式定義讓 isDefined 具有靈活性,可以處理不同類型的資料結構,不僅限於 Vue 的 refcomputed,也可以應用於普通變數。


3. 實際的函式實作

最後的實作部分其實非常簡單:

export function isDefined<T>(v: Ref<T>): boolean {
  return unref(v) != null
}

這段程式碼通過 unref 將傳入的值取出,然後與 null 進行比較。只要這個值不是 nullisDefined 就會返回 true,否則返回 false

補充 TypeScript 的型別收縮(Type Narrowing)

在 TypeScript 中,類型收縮(Type Narrowing) 是指在程式運行過程中,TypeScript 根據條件判斷推斷出變數更精確的類型。這可以讓我們在編寫程式時獲得更好的型別提示和錯誤檢查,避免類型錯誤。這對於處理多種可能類型的變數特別有用。

例如,當你有一個變數可能是多種不同的類型(比如 string | undefined),TypeScript 會自動進行類型收縮,當特定條件(如檢查是否為 undefined)成立時,它會推斷該變數的類型變得更加具體。

類型收縮的常見方式

  1. 使用 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))
      }
    }
    
    
  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()
      }
    }
    
    
  3. 使用相等檢查 (線上玩玩看這段程式碼)
    當你使用 ===!== 進行值的檢查時,TypeScript 會收縮類型:

    function printValue(value: string | null) {
      if (value !== null) {
        // 這裡 TypeScript 知道 value 是 string
        console.log(value)
      }
    }
    
    
  4. 自定義的類型守衛(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 確認一個值是否已定義(不是 nullundefined)。這個函式的特點是,它在判斷值是否為 nullundefined 的同時,自動幫助 TypeScript 收縮變數的類型。

例如,以下範例中的 example 是一個可能是 stringundefinedRef

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。這樣一來,我們不需要在每次取值時都做 nullundefined 的檢查。

總結

有些讀者可能會疑惑:既然透過 TypeScript 的型別收縮就能達成目標,為何還要使用 VueUse 來解決?主要原因是它可以有效提高程式碼的可讀性(因為做得到)。在這方面,我推薦一本書:易讀程式之美學:提升程式碼可讀性的簡單法則

isDefined 這個函式雖然簡單,卻巧妙地封裝了多個 TypeScript 技巧。這正是筆者選擇它作為首個介紹對象的原因。它沒有華麗的技巧,而是以簡潔的 TypeScript 封裝展現了 VueUse 的美。


上一篇
D-03 isDefined 文件說明與範例 — 簡單感受一下 VueUse 的美
下一篇
D-05 useVModel 文件說明與範例 — 實用性很高の函式之一
系列文
不會 VueUse 而被提分手的我30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言