溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

TypeScript中條件類型精讀與實踐記錄是怎樣的

發布時間:2021-10-08 09:31:27 來源:億速云 閱讀:165 作者:柒染 欄目:開發技術

本篇文章為大家展示了TypeScript中條件類型精讀與實踐記錄是怎樣的,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

    在大多數程序中,我們必須根據輸入做出決策。TypeScript 也不例外,使用條件類型可以描述輸入類型與輸出類型之間的關系。

    用于條件判斷時的 extends

    當 extends 用于表示條件判斷時,可以總結出以下規律

    若位于 extends 兩側的類型相同,則 extends 在語義上可理解為 ===,可以參考如下例子:

    type result1 = 'a' extends 'abc' ? true : false // false
    type result2 = 123 extends 1 ? true : false     // false

    若位于 extends 右側的類型包含位于 extends 左側的類型(即狹窄類型 extends 寬泛類型)時,結果為 true,反之為 false??梢詤⒖既缦吕?

    type result3 = string extends string | number ? true : false // true

    當 extends 作用于對象時,若在對象中指定的 key 越多,則其類型定義的范圍越狹窄??梢詤⒖既缦吕?

    type result4 = { a: true, b: false } extends { a: true } ? true : false // true

    在泛型類型中使用條件類型

    考慮如下 Demo 類型定義:

    type Demo<T, U> = T extends U ? never : T

    結合用于條件判斷時的 extends,可知 'a' | 'b' | 'c' extends 'a' 是 false, 因此 Demo<'a' | 'b' | 'c', 'a'> 結果是 'a' | 'b' | 'c' 么?
    查閱官網,其中有提到:

    When conditional types act on a generic type, they become distributive when given a union type.

    即當條件類型作用于泛型類型時,聯合類型會被拆分使用。即 Demo<'a' | 'b' | 'c', 'a'> 會被拆分為 'a' extends 'a'、'b' extends 'a'、'c' extends 'a'。用偽代碼表示類似于:

    function Demo(T, U) {
      return T.map(val => {
        if (val !== U) return val
        return 'never'
      })
    }
    
    Demo(['a', 'b', 'c'], 'a') // ['never', 'b', 'c']

    此外根據 never 類型的定義 —— never 類型可分配給每種類型,但是沒有類型可以分配給 never(除了 never 本身)。即 never | 'b' | 'c' 等價于 'b' | 'c'。

    因此 Demo<'a' | 'b' | 'c', 'a'> 的結果并不是 'a' | 'b' | 'c' 而是 'b' | 'c'。

    工具類型

    心細的讀者可能已經發現了 Demo 類型的聲明過程其實就是 TypeScript 官方提供的工具類型中 Exclude<Type, ExcludedUnion> 的實現原理,其用于將聯合類型 ExcludedUnion 排除在 Type 類型之外。

    type T = Demo<'a' | 'b' | 'c', 'a'> // T: 'b' | 'c'

    基于 Demo 類型定義,進一步地還可以實現官方工具類型中的 Omit<Type, Keys>,其用于移除對象 Type
    中滿足 keys 類型的屬性值。

    type Omit<Type, Keys> = {
      [P in Demo<keyof Type, Keys>]: Type<P>
    }
    
    interface Todo {
      title: string;
      description: string;
      completed: boolean;
    }
    
    type T = Omit<Todo, 'description'> // T: { title: string; completed: boolean }

    逃離艙

    如果想讓 Demo<'a' | 'b' | 'c', 'a'> 的結果為 'a' | 'b' | 'c' 是否可以實現呢? 根據官網描述:

    Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.

    如果不想遍歷泛型中的每一個類型,可以用方括號將泛型給括起來以表示使用該泛型的整體部分。
    type Demo<T, U> = [T] extends [U] ? never : T

    type Demo<T, U> = [T] extends [U] ? never : T
    
    // result 此時類型為 'a' | 'b' | 'c'
    type result = Demo<'a' | 'b' | 'c', 'a'>

    在箭頭函數中使用條件類型

    在箭頭函數中使用三元表達式時,從左向右的閱讀習慣導致函數內容區若不加括號則會讓使用方感到困惑。比如下方代碼中 x 是函數類型還是布爾類型呢?

    // The intent is not clear.
    var x = a => 1 ? true : false

    在 eslint 規則 no-confusing-arrow 中,推薦如下寫法:

    var x = a => (1 ? true : false)

    在 TypeScript 的類型定義中,若在箭頭函數中使用 extends 也是同理,由于從左向右的閱讀習慣,也會導致閱讀者對類型代碼的執行順序感到困惑。

    type Curry<P extends any[], R> =
      (arg: Head<P>) => HasTail<P> extends true ? Curry<Tail<P>, R> : R

    因此在箭頭函數中使用 extends 建議加上括號,對于進行 code review 有很大的幫助。

    type Curry<P extends any[], R> =
      (arg: Head<P>) => (HasTail<P> extends true ? Curry<Tail<P>, R> : R)

    結合類型推導使用條件類型

    在 TypeScript 中,一般會結合 extends 來使用類型推導 infer 語法。使用它可以實現自動推導類型的目的。比如用其來實現工具類型 ReturnType<Type> ,該工具類型用于返回函數 Type 的返回類型。
    type ReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never

    type ReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never
    
    MyReturnType<() => string>          // string
    MyReturnType<() => Promise<boolean> // Promise<boolean>

    結合 extends 與類型推導還可以實現與數組相關的 Pop<T>、Shift<T>、Reverse<T> 工具類型。

    Pop<T>:

    type Pop<T extends any[]> = T extends [...infer ExceptLast, any] ? ExceptLast : never
    
    type T = Pop<[3, 2, 1]> // T: [3, 2]

    Shift<T>:

    type Shift<T extends any[]> = T extends [infer _, ...infer O] ? O : never
    
    type T = Shift<[3, 2, 1]> // T: [2, 1]

    Reverse<T>

    type Reverse<T> = T extends [infer F, ...infer Others]
      ? [...Reverse<Others>, F]
      : []
    
    type T = Reverse<['a', 'b']> // T: ['b', 'a']

    使用條件類型來判斷兩個類型完全相等

    我們也可以使用條件類型來判斷 A、B 兩個類型是否完全相等。當前社區上主要有兩種方案:

    方案一: 參考 issue。

    export type Equal1<T, S> =
      [T] extends [S] ? (
        [S] extends [T] ? true : false
      ) : false

    目前該方案的唯一缺點是會將 any 類型與其它任何類型判為相等。

    type T = Equal1<{x:any}, {x:number}> // T: true

    方案二: 參考 issue。

    export type Equal2<X, Y> =
      (<T>() => T extends X ? 1 : 2) extends
      (<U>() => U extends Y ? 1 : 2) ? true : false

    目前該方案的唯一缺點是在對交叉類型的處理上有一點瑕疵。

    type T = Equal2<{x:1} & {y:2}, {x:1, y:2}> // false

    以上兩種判斷類型相等的方法見仁見智,筆者在此拋磚引玉。

    上述內容就是TypeScript中條件類型精讀與實踐記錄是怎樣的,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女