TypeScript 題
在讀深入理解typescript讀到infer這張遇到的這個題鸟辅,最后參考當(dāng)前文章
我們看完這些,我們發(fā)現(xiàn)typescript可以減少90%的一些拼寫、字段漏寫多律、傳參不匹配等基本錯誤崖疤。還可以增減代碼的健壯性移层,代碼的可維護性提升聂抢。
這個題目是一個很有代表意義的題蒂窒,在實際工作中也會遇到類似的問題。
問題定義
假設(shè)有一個叫EffectModule
的類
假設(shè)有一個叫 EffectModule 的類
這個對象上的方法只可能有兩種類型簽名:
interface Action<T> {
payload?: T
type: string
}
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>
syncMethod<T, U>(action: Action<T>): Action<U>
這個對象上還可能有一些任意的非函數(shù)屬性:
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"
};
}
}
現(xiàn)在有一個叫connect
的函數(shù)幔亥,它接受EffectModule
實例耻讽,將它變成另一個對象,這個對象上只有EffectModule
的同名方法帕棉,但是方法的類型簽名被改變了:
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 之后:
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.ts
中 connected
的類型與:
type Connected = {
delay(input: number): Action<string>;
setMessage(action: Date): Action<number>;
}
完全匹配。
解答
- 挑選函數(shù)
- 取待推斷的變量類型
補充
type FuncName<T> = { [P in keyof T]: T[P] extends Function ? P : never }[keyof T];
詳細
題目要求是把 type Connect 中的 any 替換掉,并符合:
type Connected = {
delay(input: number): Action<string>;
setMessage(action: Date): Action<number>;
}
并編譯通過即纲。
再把問題簡化一下捺僻,就是設(shè)計一個工具類型,讓題目中的EffectModule
的實例轉(zhuǎn)化為符合要求的Connected
崇裁。
即:
type Connect = (module: EffectModule) => xxx ---> type Connected = {
delay(input: number): Action<string>;
setMessage(action: Date): Action<number>;
}
仔細觀察上面的偽代碼實例,Connected
其實是一個對象類型,其中包含的 key-value
就是EffectModule
中的方法轉(zhuǎn)化而來的束昵,所以我們的入手處就是想辦法將EffectModule
中的方法轉(zhuǎn)化為對應(yīng)的Connected
中的 key-value
拔稳。
type Connect = (module: EffectModule) => {
...
}
再觀察 Connected 的屬性與 EffectModule 的方法是不是有共同之處?他們的名字是一樣的,所以我們得先設(shè)計一個工具類型把 EffectModule 中的方法名取出來锹雏。
但EffectModule
,包含非方法的「屬性」巴比,所以得做個判斷,如果是屬性值類型是函數(shù)那么取出,否則不要取出
type FuncName<T> = { [P in keyof T]: T[P] extends Function ? P : never }[keyof T];
方法的類型簽名被改變了:
asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>> 變成了
asyncMethodConnect<T, U> = (input: T) => Action<U>
syncMethod<T, U> = (action: Action<T>) => Action<U> 變成了
syncMethodConnect<T, U> = (action: T) => Action<U>
著手轉(zhuǎn)化工作
type EffectModuleMethodsConnect<T> = T extends asyncMethod<infer U, infer V>
? asyncMethodConnect<U, V>
: T extends syncMethod<infer U, infer V>
? syncMethodConnect<U, V>
: never;
目前我們有兩個主要的工具類型EffectModuleMethodsConnect
負責(zé)類型的轉(zhuǎn)化,methodsPick
負責(zé)取出方法名,現(xiàn)在我們先把方法名取出:
type EffectModuleMethods = FuncName<EffectModule>
最后
type Connect = (module: EffectModule) => {
[M in EffectModuleMethods]: EffectModuleMethodsConnect<EffectModule[M]>
}
// import { expect } from "chai";
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"
};
}
}
type MethodPick<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>;
type asyncMethodConnect<T, U> = (input: T) => Action<U>;
type syncMethod<T, U> = (input: Action<T>) => Action<U>;
type syncMethodConnect<T, U> = (input: T) => Action<U>;
type EffectModuleMethodsConnect<T> = T extends asyncMethod<infer U, infer V>
? asyncMethodConnect<U, V>
: T extends syncMethod<infer U, infer V>
? syncMethodConnect<U, V>
: never;
type EffectModuleMethods = MethodPick<EffectModule>;
// 修改 Connect 的類型轻绞,讓 connected 的類型變成預(yù)期的類型
type Connect = (module: EffectModule) => {
[M in EffectModuleMethods]: EffectModuleMethodsConnect<EffectModule[M]>
};
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>;
};
export const connected: Connected = connect(new EffectModule());