理解TypeScript中的infer關鍵字

前言

infer是在typescript 2.8中新增的關鍵字,距離如今3.9.3已經有兩年出頭了,趁着今天恰好使用了infer,因此好好整理一番javascript

infer

infer能夠在extends的條件語句中推斷待推斷的類型html

例如在文檔的示例中,使用infer來推斷函數的返回值類型java

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

type func = () => number;
type variable = string;
type funcReturnType = ReturnType<func>; // funcReturnType 類型爲 number
type varReturnType = ReturnType<variable>; // varReturnType 類型爲 string
複製代碼

在這個例子中,infer R表明待推斷的返回值類型,若是T是一個函數,則返回函數的返回值,不然返回anygit

僅僅經過這一個例子,是很難看出infer是用來幹什麼的,還須要多看幾個例子github

infer解包

infer的做用不止是推斷返回值,還能夠解包,我以爲這是比較經常使用的typescript

假如想在獲取數組裏的元素類型,在不會infer以前我是這樣作的數組

type Ids = number[];
type Names = string[];

type Unpacked<T> = T extends Names ? string : T extends Ids ? number : T;

type idType = Unpacked<Ids>; // idType 類型爲 number
type nameType = Unpacked<Names>; // nameType 類型爲string
複製代碼

上次我寫了20多行,就爲了獲取一堆各類不一樣類型的數組裏的元素類型,然而若是使用infer來解包,會變得十分簡單函數

type Unpacked<T> = T extends (infer R)[] ? R : T;

type idType = Unpacked<Ids>; // idType 類型爲 number
type nameType = Unpacked<Names>; // nameType 類型爲string
複製代碼

這裏T extends (infer R)[] ? R : T的意思是,若是T是某個待推斷類型的數組,則返回推斷的類型,不然返回Tpost

再好比,想要獲取一個Promise<xxx>類型中的xxx類型,在不使用infer的狀況下我想不到何解學習

type Response = Promise<number[]>;
type Unpacked<T> = T extends Promise<infer R> ? R : T;

type resType = Unpacked<Response>; // resType 類型爲number[]
複製代碼

infer推斷聯合類型

仍是官方文檔的例子

type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;

type T10 = Foo<{ a: string; b: string }>; // T10類型爲 string
type T11 = Foo<{ a: string; b: number }>; // T11類型爲 string | number
複製代碼

同一個類型變量在推斷的值有多種狀況的時候會推斷爲聯合類型,針對這個特性,很方便的能夠將元組轉爲聯合類型

type ElementOf<T> = T extends (infer R)[] ? R : never;

type TTuple = [string, number];
type Union = ElementOf<TTuple>; // Union 類型爲 string | number
複製代碼

React中infer的使用

Reacttypescript源碼中應該經常使用infer

就拿useReducer來舉例子,若是咱們這樣使用useReducer

const reducer = (x: number) => x + 1;
const [state, dispatch] = useReducer(reducer, '');
// Argument of type "" is not assignable to parameter of type 'number'.
複製代碼

這裏useReducer會報一個類型錯誤,說""不能賦值給number類型

那麼React這裏是如何經過reducer函數的類型來判斷state的類型呢?

查看userReducer的定義,定義以下

function useReducer<R extends Reducer<any, any>, I>(
  reducer: R,
  // ReducerState 推斷類型
  initializerArg: I & ReducerState<R>,
  initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

// infer推斷
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
  ? S
  : never;
// Reducer類型
type Reducer<S, A> = (prevState: S, action: A) => S;
複製代碼

一切明瞭了,使用了infer推斷reducer函數裏的state參數類型

我今天碰見的問題

今天使用ant-design-chart,庫裏沒有把Ref的定義導出,因此只能本身取了

// 已知
type ref = React.MutableRefObject<G2plotStackedBar | undefined>;
// 求 ???
const chartRef = useRef<???>()
複製代碼

有了上面的學習,這裏就很簡單了,只須要取出React.MutableRefObject裏的內容,一行infer搞定

// infer推斷
type ChartRef<T> = T extends React.MutableRefObject<infer P> ? P : never;
                                                    
const chartRef = useRef<ChartRef<ref>>()
複製代碼

總結

infer是很是有用的,若是想要擺脫僅僅是在寫帶類型的javascript,高級特性必定要了解

我可能一年前就看見infer了,一直沒有好好學,緣由除了本身懶,還有就是水平確實不夠,今年再學明顯感受不一樣了。

再推薦一篇很好的文章,我也是看了這篇文章纔好好學習了一下infer,這篇文章講的更復雜一點

Vue3 跟着尤雨溪學 TypeScript 之 Ref 類型從零實現

題外話 分享一道比較複雜的練習題

原題就不貼出了,在這裏能夠看見 github

分享一下個人思路

  1. 首先先取得函數的名字,經過extends關鍵字能夠判斷是不是函數,是返回鍵名,不是返回never,最後使用映射類型[keyof T]的方式來獲取鍵名的聯合類型,由於never和任何類型組聯合類型都會過濾掉never,因此天然排除了never
  2. 就用infer硬推

題解以下:

type EffectModuleFuncName = {
  [K in keyof EffectModule]: EffectModule[K] extends Function ? K : never;
}[keyof EffectModule];

type UnPackedPromise<T> = T extends Promise<infer P> ? P : T;

type EffectModuleFunc<T> = T extends (params: infer P) => infer U
  ? P extends Promise<infer R>
    ? (v: R) => UnPackedPromise<U>
    : P extends Action<infer X>
    ? (v: X) => UnPackedPromise<U>
    : never
  : never;

// 修改 Connect 的類型,讓 connected 的類型變成預期的類型
type Connect = (
  module: EffectModule,
) => { [K in EffectModuleFuncName]: EffectModuleFunc<EffectModule[K]> };
複製代碼

也不知道本身寫的對不對,總以爲怪怪的,能夠討論一下


參考資料:

  1. TypeScript文檔
  2. 深刻理解TypeScript

最後,祝你們身體健康,工做順利!

歡迎你們關注個人公衆號~

相關文章
相關標籤/搜索