欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

TypeScript逆變之條件推斷和泛型的應用示例詳解

 更新時間:2023年09月19日 09:51:27   作者:塵緣  
這篇文章主要為大家介紹了TypeScript逆變之條件推斷和泛型的應用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

一個類型問題

有一個名為 test 的函數(shù),它接受兩個參數(shù)。第一個參數(shù)是函數(shù) fn,第二個參數(shù) options 受到 fn 參數(shù)的限制。乍一看,這個問題貌似并不復雜,不是嗎?糊業(yè)務的時候,這種不是常見的需求嘛。

“創(chuàng)建一個泛型類型 Test,以確保這兩個參數(shù)之間存在約束關系就完事了,睡醒再說”,就這樣暗忖著,又昏昏沉沉睡過去,只有那 T extends unknown[]闖入我夢中,飄忽不定,若即若離,暗示著我再次翻車【看題時覺得簡單,解題時頭大如?!康拿\。

下面我們先來看看題目:

type InjectorFunction<P> = () => P;
interface Options<P> {
  injector: InjectorFunction<P>;
}
const fn1 = () => 1;
const fn2 = (p: number) => `number is: ${p}!`;
const fn3 = (p: string) => `hello ${p}!`;
const fn4 = (p?: string) => `hello ${p || 'fn4'}!`;
type Test<F extends (...args: any[]) => any = any> = (fn: F, options?: Options<Parameters<F>>) => void;
const test: Test = (fn, options) => {
  return fn(options?.injector?.());
}
// 定義 Test 函數(shù)的類型,使得下面類型成立
test(fn1);                                  // right
test(fn1, { injector: () => {} });          // error, dont need injector
test(fn2, { injector: () => 4 });           // right
test(fn3, { injector: () => 'world' });     // right
test(fn3);                                  // error, options.injector is required
test(fn4);                                  // right
test(fn4, { injector: () => 'test4' });     // right

在繼續(xù)往下翻閱之前,先來typescript playground 玩呀,兄弟們。也可以用來配合本文食用哦。

題目規(guī)則和解法

閱讀代碼中的注釋,我們可以得出以下題目描述和要求:

考慮函數(shù) test,它有兩個參數(shù)。第一個參數(shù)必然是一個函數(shù) fn,而第二個參數(shù) options 受到 fn 約束,其泛型參數(shù)是 fn的參數(shù)類型。

  • 如果 fn 沒有參數(shù),則 test 不能有第二個參數(shù) options
  • 如果 fn 有一個參數(shù) p,則 test 必須有第二個參數(shù) options。
  • 如果 fn 的參數(shù) p 是可選的,則第二個參數(shù) options 也是可選的。
  • options是個泛型 Options<T>, T的類型是 fn 的參數(shù) p的類型。

在觀察前三個規(guī)則后,我們初步得出了一個類似于下面結構的 test 函數(shù),其中推斷參數(shù)個數(shù)的部分需要延遲:

type Test = (...arg: unknown[]) => unknown

我們知道,使用泛型類型或條件類型可以幫助實現(xiàn)參數(shù)之間的約束關系。而題目中已經定義好的Test類型中,type Test<F extends (...args: any[]) => any = any> = (fn: F, options?: Options<Parameters<F>>) => void;options直接定義為可選的,并不能符合第一和第二條規(guī)則。

我們需要創(chuàng)建一個名為 Args<T> 的工具類型,它用于動態(tài)生成 test 函數(shù)的參數(shù)。盡管我們目前使用泛型來描述這些參數(shù),但是我們可以使用偽代碼 [FN, Opts] 來暫時表示未完成的實現(xiàn)。具體而言,我們將 fn 參數(shù)的類型稱為 FN,將 options 參數(shù)的類型稱為 Opts。

type Test = <T>(...arg: Args<T>) => unknown

首先, T 必須是個數(shù)組,如果不是數(shù)組,那它就沒存在的必要了,如果是,我們先返回兩個參數(shù)組成的數(shù)組好不啦。現(xiàn)在,可以用上前面起的小名了!略西!

type Args<T> = T extends unknown[] ? [FN, Opts] : never

其次,第一個參數(shù)必然是 fn,我們需要判斷它的參數(shù)形狀。先從最簡單的 fn 沒有參數(shù)開始。

type Args<T> = T extends unknown[] ?
    T[0] extends () => number ? [() => number]: [FN, Opts] : never

下一步,我們需要判斷 T[0] 是個帶有參數(shù)的函數(shù)。T[0] 是 (arg: SomeType) => unknown嗎?如果是,我們還要把 SomeType 添加到 [FN, Opts]。還記得前文第四個規(guī)則嗎,小 Opts 是個泛型,是個參數(shù)和 FN參數(shù)一致的泛型。

在條件類型表達式中,infer 關鍵字用來聲明一個待推斷的類型變量,將其用于 extends 條件語句中。這樣可以使 TypeScript 推斷出特定位置的類型,并將其應用于類型判斷和條件分支中。

因此,我們可以用這個條件語句 T[0] extends (arg: infer P) => string 來表示T[0] 可以賦值給 (arg: infer P) => string。在這個條件語句中,我們使用 infer P 來聲明一個類型變量 P,它用于描述 fn 的參數(shù)類型以及 Options<T> 泛型的參數(shù)類型。

type Args<T> =
  T extends unknown[] ?
    T[0] extends () => number ? [() => number]:
    T[0] extends (arg: infer P) => string ? [(arg: P) => string, Options<P>] : [FN, Opts]
  : never

在這一步,我們還需要解決一個問題,即如何判斷參數(shù)是否為可選類型。

要獲取函數(shù)的參數(shù),我們可以使用 TypeScript 內置的 Parameters 類型。

Parameters<T> 類型接受一個函數(shù)類型 T,并返回該函數(shù)類型的參數(shù)類型元組。通過檢查 Parameters<T> 元組的長度和元素類型,我們可以判斷參數(shù)的個數(shù)和類型,并根據(jù)需要進行相應處理。

type GetParamsNum<T extends (...args: any) => any> = Parameters<T>['length'];

要判斷參數(shù)形狀是哪種,即有、無或薛定諤的有/無(即參數(shù)個數(shù)可以是 0,也可以是 1,或者是 0 | 1),我們可以使用以下代碼來區(qū)分這三種情況:01,0 | 1

type GetParamShape<T> =
  [T] extends [0] ? "無" :
  [T] extends [1] ? "有" : "薛定諤的有/無"

綜上所述,讓我們進一步分解這個分支:T[0] extends (arg: infer P) => string,Args 類型已經完全展開,我們可以得到以下結論:

  • 當 T[0] 能夠賦值給 (arg: infer P) => string 時,我們可以推斷出參數(shù)類型 P 是函數(shù) T[0] 的參數(shù)類型。
  • 通過 Parameters<T[0]>,我們可以獲取函數(shù) T[0] 的參數(shù)類型元組。
  • 通過判斷 [Parameters<T[0]>['length']] extends[1],我們得到函數(shù) T[0] 必然有一個參數(shù)的分支,從而返回預期的類型 [(arg: P) => string, Options<P>]
  • 如果條件不符合,返回預期的類型 [(arg?: P) => unknown, Options<P>?], arg是可選的,Options也是可選的。

Args 類型的完整定義如下:

type Args<T> =
  T extends unknown[] ?
  T[0] extends () => number ? [() => number]:
  T[0] extends (arg: infer P) => string ? [Parameters<T[0]>['length']] extends[1] ? [(arg: P) => string, Options<P>] : 
  [(arg?: P) => unknown, Options<P>?]
  : never  : never

現(xiàn)在,根據(jù)前面的 type Test = <T>(...arg: Args<T>) => unknown,讓我們對 test 函數(shù)進行進一步改造。

type Test = <T>(...arg: Args<T>) => unknown
const test: Test = (...args) => {
  const [fn, options] = args
  return fn(options?.injector?.())
}

在這個改造后的 test 函數(shù)中,我們接受一個參數(shù)數(shù)組 args,其中包含了函數(shù) fn 和 options 參數(shù)。我們使用數(shù)組解構賦值將這兩個參數(shù)提取出來。

我們已經完成了類型定義的重新定義以及函數(shù)的改造,現(xiàn)在讓我們來看看是否能夠得到預期的類型推斷和錯誤。

第一次翻車

1.png

每個調用都報錯了。一個方案是在調用的時候指定泛型參數(shù),但這樣做就很麻煩,并且毫不意外地被大佬嫌棄了。那就開始對 Test 進行進一步改造。

這次的改造將進一步簡化 Args 類型,使其看起來更加一目了然。它接受一個泛型參數(shù) T,該參數(shù)是一個數(shù)組類型,表示函數(shù)的參數(shù)列表。根據(jù)不同的參數(shù)個數(shù),我們進行不同的類型轉換:

  • 如果參數(shù)列表為空,即 T extends [],則表示函數(shù)沒有參數(shù)。在這種情況下,test沒有其他參數(shù),即 []。
  • 如果參數(shù)列表只有一個元素 P,即 T extends [infer P],則表示函數(shù)只有一個參數(shù)。我們將該參數(shù)的類型進行轉換為 Options<P>,即一個帶有 P 類型的 Options 類型的元組,即 [Options<P>]
  • 對于其他情況,我們將整個參數(shù)列表定義為一個可選的 Options<string> 類型的元組,即 [Options<string>?]。

最后,我們定義了一個 Test 類型,它是一個高階函數(shù)類型,接受一個函數(shù) T 作為第一個參數(shù),以及根據(jù)函數(shù)參數(shù)列表進行轉換的元組類型 Args<Parameters<T>>。該類型表示函數(shù)的參數(shù)列表可能有多個,并且根據(jù)參數(shù)個數(shù)的不同應用不同的轉換類型。現(xiàn)在,我們就可以直接傳入函數(shù) fn 和它的參數(shù)來調用 Test 函數(shù),不再需要在每次調用的時候指定 fn 類型。

type Args<T extends unknown[]> =
  T extends [] ? [] :
  T extends [infer P] ? [Options<P>] : [Options<T[0]>?]
type Test = <T extends (...arg: any[]) => unknown>(...args: [T, ...Args<Parameters<T>>]) => unknown

這里用上了any 和 unknown,給泛型T指定為帶有任意參數(shù)的函數(shù)類型。應該避免使用萬能類型 any,因為它繞過了類型檢查,降低了類型安全性。然而在此處,我們無法替換 any 為 unknown類型的位置影響逆變協(xié)變,函數(shù)參數(shù)通常處于逆變的位置,子類型(更具體的類型)不能賦值給父類型(更寬泛的類型)。而unknown 是所有類型的父類型。

看廣場吧,期待其它解法分享啊兄弟們。等你們來玩啊。

真正的規(guī)則

  • 當 fn 沒有參數(shù)時,options 是可選的,但沒有 injector 字段。
  • 當 fn 有參數(shù)且參數(shù)為必填時,options.injector 也是必填的,且injector 的返回類型為 fn 的參數(shù)類型。
  • 當 fn 有參數(shù)但參數(shù)為可選時,options 是可選的,injector 也是可選的,且返回字符串。
  • options可能有其它屬性,但具體是什么屬性并沒有明確指定。因此,我們可以假設其他屬性只有一個 weight 屬性。

預期錯誤如下所示:

// 定義 Test 函數(shù)的類型,使得下面類型成立
test(fn1);                                  // right
test(fn1, { weight: 10 });                  // right
test(fn1, { injector: () => {} });          // error, dont need injector
test(fn2, { injector: () => 4 });           // right
test(fn3, { injector: () => 'world' });     // right
test(fn3);                                  // error, options.injector is required
test(fn3, { injector: () => 4 });           // error
test(fn4);                                  // right
test(fn4, { injector: () => 'test4' });     // right
test(fn4, { injector: () => undefined });   // error

為了符合上述規(guī)則,我們對泛型工具類型 Args進行了一些分支上的改造處理:

  • 如果 fn 參數(shù)列表為空,即 T extends [],則剩余的參數(shù)列表定義為一個可選的 OtherOpts 類型的元組,即 [OtherOpts?]。
  • 如果fn參數(shù)列表只有一個元素 P,即 T extends [infer P]。我們將該參數(shù)的類型進行轉換為 Options<P>,指定 options.injector 的返回類型為 fn 參數(shù)類型 P
  • 對于其它情況,我們將整個參數(shù)列表定義為一個可選的 Options<string> 類型的元組,即 [Options<string>?]

Test 高階函數(shù)類型保持不變。

interface OtherOpts  {
  weight: number;
}
type Args<T extends unknown[]> =
  T extends [] ? [OtherOpts?] :
  T extends [infer P] ? [Options<P>] : [Options<string>?]

以上就是TypeScript逆變之條件推斷和泛型的應用示例詳解的詳細內容,更多關于TypeScript逆變條件推斷泛型的資料請關注腳本之家其它相關文章!

相關文章

  • 適合面向ChatGPT編程的架構示例詳解

    適合面向ChatGPT編程的架構示例詳解

    這篇文章主要為大家介紹了適合面向ChatGPT編程的架構示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • TypeScript類型any never void和unknown使用場景區(qū)別

    TypeScript類型any never void和unknown使用場景區(qū)別

    這篇文章主要為大家介紹了TypeScript類型any never void和unknown使用場景區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • Rollup 簡易入門示例教程

    Rollup 簡易入門示例教程

    這篇文章主要為大家介紹了Rollup 簡易入門示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • Three.js?粗糙度貼圖與金屬度貼圖使用介紹

    Three.js?粗糙度貼圖與金屬度貼圖使用介紹

    這篇文章主要為大家介紹了Three.js?粗糙度貼圖與金屬度貼圖使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • TypeScript開發(fā)HapiJS應用詳解

    TypeScript開發(fā)HapiJS應用詳解

    這篇文章主要為大家介紹了TypeScript開發(fā)HapiJS應用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • TypeScript防抖節(jié)流函數(shù)示例詳解

    TypeScript防抖節(jié)流函數(shù)示例詳解

    這篇文章主要為大家介紹了TypeScript防抖節(jié)流函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • TypeScript Module Resolution解析過程

    TypeScript Module Resolution解析過程

    這篇文章主要為大家介紹了TypeScript Module Resolution解析過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • Spartacus中navigation?item?reducer實現(xiàn)解析

    Spartacus中navigation?item?reducer實現(xiàn)解析

    這篇文章主要為大家介紹了Spartacus中navigation?item?reducer實現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • TypeScript十大排序算法插入排序實現(xiàn)示例詳解

    TypeScript十大排序算法插入排序實現(xiàn)示例詳解

    這篇文章主要為大家介紹了TypeScript十大排序算法插入排序實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • TypeScript十大排序算法之選擇排序實現(xiàn)示例詳解

    TypeScript十大排序算法之選擇排序實現(xiàn)示例詳解

    這篇文章主要為大家介紹了TypeScript十大排序算法之選擇排序實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02

最新評論