一道ts类型编程题的解答

一道 TypeScript 题

问题定义

假设有一个叫 EffectModule 的类

1
class EffectModule {}

这个对象上的方法只可能有两种类型签名:

1
2
3
4
5
6
7
8
interface Action<T> {
payload?: T
type: string
}

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

syncMethod<T, U>(action: Action<T>): Action<U>

这个对象上还可能有一些任意的非函数属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 的函数,它接受 EffectModule 实例,将它变成另一个一个对象,这个对象上只有EffectModule 的同名方法,但是方法的类型签名被改变了:

1
2
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>  变成了
asyncMethod<T, U>(input: T): Action<U>
1
2
syncMethod<T, U>(action: Action<T>): Action<U>  变成了
syncMethod<T, U>(action: T): Action<U>

例子:

EffectModule 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 之后:

1
2
3
4
5
6
type Connected = {
delay(input: number): Action<string>
setMessage(action: Date): Action<number>
}
const effectModule = new EffectModule()
const connected: Connected = connect(effectModule)

要求

题目链接 里面的 index.ts 文件中,有一个 type Connect = (module: EffectModule) => any,将 any 替换成题目的解答,让编译能够顺利通过,并且 index.tsconnected 的类型与:

1
2
3
4
type Connected = {
delay(input: number): Action<string>;
setMessage(action: Date): Action<number>;
}

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

type OriAsyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>;
type AsyncMethod<T, U> = (input: T) => Action<U>;

type OriSyncMethod<T, U> = (action: Action<T>) => Action<U>;
type SyncMethod<T, U> = (action: T) => Action<U>;

// 首先根据题目写出对应的类型

type PickFuncKeys<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type FuncKeys = PickFuncKeys<EffectModule>;

// 取出值为函数的键名,这一步是必须的,如果直接对处理原对象所有的键
// 会得到额外的属性,也就是带上
// count: never;
// age: never;
// 的结果,是无法通过编译的
// 而题目要求结果仅仅具有方法,不具有属性,所以带有额外属性也是错误的,必须仅处理值为函数的键,忽略其他的键名
// type Connected = { 题目要求
// delay(input: number): Action<string>;
// setMessage(action: Date): Action<number>;
// };


type Connect = (module: EffectModule) => { // 替换结果
[K in FuncKeys]: EffectModule[K] extends OriAsyncMethod<infer A, infer B>
? AsyncMethod<A, B>
: EffectModule[K] extends OriSyncMethod<infer A, infer B>
? SyncMethod<A, B>
: never;
}