再次研究一道網紅typescript面試題

題目描述

原題地址 有一個叫EffectModule的類git

class EffectModule {}


複製代碼

這個對象上有各類各樣的屬性,string、number、function等,其中他的function類型的屬性只可能有兩種類型簽名:github

asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>
syncMethod<T, U>(action: Action<T>): Action<U>
複製代碼

如今有一個叫 connect 的函數,它接受 EffectModule實例,將它變成另外一個對象,這個對象上只有EffectModule 的同名方法,可是方法的類型簽名被改變了:typescript

interface Action<T> {
  payload?: T
  type: string
}

asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>
變成了
asyncMethod<T, U>(input: T): Action<U> 

syncMethod<T, U>(action: Action<T>): Action<U>
變成了
syncMethod<T, U>(action: T): Action<U>

複製代碼

例子: EffectModule 定義以下:數組

interface Action<T> {
  payload?: T;
  type: string;
}

class EffectModule {
  count = 1;
  message = "hello!";

  delay(input: Promise<number>) {
    return input.then(i => ({
      payload: `hello ${i}!`,
      type: 'delay'
    }));
  }

  setMessage(action: Action<Date>) {
    return {
      payload: action.payload!.getMilliseconds(),
      type: "set-message"
    };
  }
}
複製代碼

當connect以後:promise

// 問題就是把any換成解答,使得ts編譯正常
// 也就是讓類型Connect的返回值和Connected徹底同樣,ts編譯經過
type Connect = (module: EffectModule) => any

const connect: Connect = m => ({
  delay: (input: number) => ({
    type: 'delay',
    payload: `hello 2`
  }),
  setMessage: (input: Date) => ({
    type: "set-message",
    payload: input.getMilliseconds()
  })
});

type Connected = {
  delay(input: number): Action<string>
  setMessage(action: Date): Action<number>
}
const effectModule = new EffectModule()
const connected: Connected = connect(effectModule)
複製代碼

要求:type Connect = (module: EffectModule) => any,把any換成解答,讓類型Connect的返回值和Connected徹底同樣,使得ts編譯正常async

很明顯,咱們須要作的事情就是:函數

  • 把EffectModule的函數類型取出來
  • 把函數的參數、返回值解promise/action

把EffectModule的函數類型取出來

說到取某些key出來,就是Pick或者Omit了。但ts又沒有相似Object.keys().filter這種方式,須要使用映射類型+never去作特殊處理。整個流程就是:映射類型 =》 若是值爲函數類型,返回key,不然返回never =》 對映射類型取值,獲得函數類型的keyui

映射類型

是指把一個類型映射爲另外一個類型,key用的是相似for in的語法,表示遍歷舊的類型裏面每個key:spa

type mapType0<T> = {
  [k in keyof T]: T[k]
}
複製代碼

在映射類型後面加上[keyof T],至關於valueof的方法了——返回的是這個類型裏面全部的key的value的聯合類型:code

const o = {
    a: 1,
    b: '2'
}

type map1 = mapType0<typeof o>[keyof typeof o]
// string | number
複製代碼

Pick和Omit

咱們知道了映射類型,基於此能夠實現一個Pick(ts其實已經自帶)

type myPick<T, K extends keyof T> = {
  [k in K]: T[k]
}
複製代碼

第二個泛型參數約束key來源於T,這樣子就能夠確保取獲得原對象某些key了。基於Pick,就能夠實現Omit

type MyOmit<T, K extends keyof any> 
= Pick<T, Exclude<keyof T, K>>;
複製代碼

獲取value爲function類型的key

type FunctionKeys<T> = {
  [k in keyof T]: T[k] extends Function ? k : never
}[keyof T]
type functionKeys = Pick<EffectModule, FunctionKeys<EffectModule>> 複製代碼

使用Omit實現

type noFunctionKeys<T> = {
  [k in keyof T]: T[k] extends Function ? never : k
}[keyof T]
type functionKeys = Omit<EffectModule, noFunctionKeys<EffectModule>> 複製代碼

如今,你獲得了一個對象,只有value爲函數類型的key了

把函數的參數、返回值解promise/action

infer

infer表示在condition type的條件語句中待推斷的類型變量,能夠理解爲解方程,infer x表示x是待求解的變量,infer至關於一個標記做用。例如returntype就是靠infer實現的

type MyReturnType<T> = T extends (...args: any[]) => infer P ? P : any;
複製代碼

再看兩個例子,好比解Promise、獲取數組的item類型:

type UnPromisify<T> = T extends Promise<infer U> ? U : T
type UnArray<T> = T extends (infer U)[] ? U : T
複製代碼

實現效果

因此基於前面,咱們能夠把函數參數、返回值提取出來,解Promise、Action

type UnPromisify<T> = T extends Promise<infer U> ? U : T
type UnAction<T> = T extends Action<infer U> ? U : T

type MapTypeToUnPromisifyAndUnAction<T extends any[]> = {
  [k in keyof T]: UnAction<UnPromisify<T[k]>>
}

type Connect = (module: EffectModule) => ({
  [functionKey in keyof functionKeys]: (input: MapTypeToUnPromisifyAndUnAction< Parameters<functionKeys[functionKey]> >[number]) => UnPromisify<ReturnType<functionKeys[functionKey]>> }) 複製代碼
所有代碼
type FunctionKeys<T> = { [k in keyof T]: T[k] extends Function ? k : never }[keyof T]
type functionKeys = Pick<EffectModule, FunctionKeys<EffectModule>> type UnPromisify<T> = T extends Promise<infer U> ? U : T type UnAction<T> = T extends Action<infer U> ? U : T type MapTypeToUnPromisifyAndUnAction<T extends any[]> = {
  [k in keyof T]: UnAction<UnPromisify<T[k]>>
}

type Connect = (module: EffectModule) => ({
  [functionKey in keyof functionKeys]: (input: MapTypeToUnPromisifyAndUnAction< Parameters<functionKeys[functionKey]> >[number]) => UnPromisify<ReturnType<functionKeys[functionKey]>> }) 複製代碼
相關文章
相關標籤/搜索