再次研究一道网红typescript面试题
题目描述
原题地址
有一个叫EffectModule
的类
class EffectModule {}
这个对象上有各种各样的属性,string、number、function等,其中他的function
类型的属性只可能有两种类型签名:
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>
syncMethod<T, U>(action: Action<T>): Action<U>
现在有一个叫 connect 的函数,它接受 EffectModule
实例,将它变成另一个对象,这个对象上只有EffectModule
的同名方法,但是方法的类型签名被改变了:
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之后:
// 问题就是把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编译正常
很明显,我们需要做的事情就是:
- 把EffectModule的函数类型取出来
- 把函数的参数、返回值解promise/action
把EffectModule的函数类型取出来
说到取某些key出来,就是Pick或者Omit了。但ts又没有类似Object.keys().filter
这种方式,需要使用映射类型+never去做特殊处理。整个流程就是:映射类型 =》 如果值为函数类型,返回key,否则返回never =》 对映射类型取值,得到函数类型的key
映射类型
是指把一个类型映射为另一个类型,key用的是类似for in的语法,表示遍历旧的类型里面每一个key:
type mapType0<T> = {
[k in keyof T]: T[k]
}
在映射类型后面加上[keyof T]
,相当于valueof的方法了——返回的是这个类型里面所有的key的value的联合类型:
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]>>
})
转载自:https://juejin.cn/post/6850418113859551239