TypeScript `infer` 關鍵字


考察如下類型:

type PromiseType<T> = (args: any[]) => Promise<T>;

那么對於符合上面類型的一個方法,如何得知其 Promise 返回的類型?

譬如對於這么一個返回 string 類型的 Promise:

async function stringPromise() {
  return "string promise";
}

RetrunType

如果你對 TypeScript 不是那么陌生,可能知道官方類型庫中提供了 RetrunType 可獲取方法的返回類型,其用法如下:

type stringPromiseReturnType = ReturnType<typeof stringPromise>; // Promise<string>

確實拿到了方法的返回類型,不過是 Promise<string>。但其實是想要返回里面的 string,所以和我們想要的還差點意思。

既然都能從一個方法反解其返回類型,肯定還能從 Promsie<T> 中反解出 T。所以不不妨看看 ReturnType 的定義:

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

F12 一看,果然發現了點什么,這里使用了 infer 關鍵字。

條件類型及 infer

上面 T extends U ? X : Y 的形式為條件類型(Conditional Types),即,如果類型 T 能夠賦值給類型 U,那么該表達式返回類型 X,否則返回類型 Y

所以,考察 ReturnType的定義,

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

如果傳入的類型 T 能夠賦值給 (...args: any) => R 則返回類型 R

但是這里類型 R 從何而來?講道理,泛型中的變量需要外部指定,即 RetrunType<T,R>,但我們不是要得到 R 么,所以不能聲明在這其中。這里 infer 便解決了這個問題。表達式右邊的類型中,加上 infer 前綴我們便得到了反解出的類型變量 R,配合 extends 條件類型,可得到這個反解出的類型 R。這里 R 即為函數 (...args: any) => R 的返回類型。

反解 Promise

有了上面的基礎,推而廣之就很好反解 Promise<T> 中的 T 了。

type PromiseType<T> = (args: any[]) => Promise<T>;

type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;

測試 UnPromisify<T>

async function stringPromise() {
  return "string promise";
}

async function numberPromise() {
return 1;
}

interface Person {
name: string;
age: number;
}

async function personPromise() {
return { name: "Wayou", age: 999 } as Person;
}

type extractStringPromise = UnPromisify<typeof stringPromise>; // string

type extractNumberPromise = UnPromisify<typeof numberPromise>; // number

type extractPersonPromise = UnPromisify<typeof personPromise>; // Person

解析參數數組的類型

反解還可用在其他很多場景,比如解析函數入參的類型。

type VariadicFn<A extends any[]> = (...args: A) => any;
type ArgsType<T> = T extends VariadicFn<infer A> ? A : never;

type Fn = (a: number, b: string) => string;
type Fn2Args = ArgsType<Fn>; // [number, string]

另一個示例

假設我們編寫了兩個按鈕組件,底層渲染的是 HTML 原生的 buttona 標簽。為了組件最大化可定制,原生元素支持的屬性該組件也需要支持,因此可這樣來寫組件的 props:

type ButtonProps = {
  color: string;
  children: React.ReactChildren;
} & React.DetailedHTMLProps<
  React.ButtonHTMLAttributes<HTMLButtonElement>,
  HTMLButtonElement
>;

type AnchorButtonProps = {
color: string;
disabled: boolean;
children: React.ReactChildren;
} & React.DetailedHTMLProps<
React.AnchorHTMLAttributes<HTMLAnchorElement>,
HTMLAnchorElement
>;

export function Button({ children, ...props }: ButtonProps) {
//...
return <button {...props}>{children}</button>;
}

export function AnchorButton({ children, ...props }: AnchorButtonProps) {
//...
return <a {...props}>{children}</a>;
}

單看 ButtonAnchorButton 的屬性,

type ButtonProps = {
  color: string;
  children: React.ReactChildren;
} & React.DetailedHTMLProps<
  React.ButtonHTMLAttributes<HTMLButtonElement>,
  HTMLButtonElement
>;

type AnchorButtonProps = {
color: string;
disabled: boolean;
children: React.ReactChildren;
} & React.DetailedHTMLProps<
React.AnchorHTMLAttributes<HTMLAnchorElement>,
HTMLAnchorElement
>;

不難看出兩者是有共性的,即可抽取成如下的形式:

type ExtendHTMLAttributes<P, T, K> = P & React.DetailedHTMLProps<T, K>;

其中 T 呢又是 T<K> 形式,即 T 中包含或有使用了 K。因此對使用者來說,如果傳遞了 T<K> 形式,就沒必要單獨再傳遞一次 K,我們應該是能利用 inferT<K> 解析出 K 的。

T extends React.HtmlHTMLAttributes<infer K> ? K : HTMLElement

所以抽取出來兩種組件 Props 可公用的一個類型如下:

export type ExtendHTMLAttributes<
  /** 組件自定義屬性 */
  P,
  /** 原生 HTML 標簽自有屬性 */
  T extends React.HtmlHTMLAttributes<HTMLElement>
> = P &
  React.DetailedHTMLProps<
    T,
    T extends React.HtmlHTMLAttributes<infer K> ? K : HTMLElement
  >;

利用抽取的 ExtendHTMLAttributes,兩種按鈕的 Props 可重新書寫成如下形式:

type ButtonProps = ExtendHTMLAttributes<
  {
    color: string;
    children: React.ReactChildren;
  },
  React.ButtonHTMLAttributes<HTMLButtonElement>
>;

type AnchorButtonProps = ExtendHTMLAttributes<
{
color: string;
disabled: boolean;
children: React.ReactChildren;
},
React.AnchorHTMLAttributes<HTMLAnchorElement>
>;

去掉了兩者重疊的部分,看起來簡潔了一些。關鍵后續編寫其他組件時,如果想支持原生 HTML 屬性,直接復用這里的 ExtendHTMLAttributes 類型即可。

相關資源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM