Utility Types Study
Methods (方法提取)
type Methods<S, T> = { [K in keyof S]?: S[K] extends (...args: infer R) => infer U ? (this: T, ...args: R) => U : S[K] }
S中成员:若为方法,则置thisArg为T,变量则不变
Constructor (构造函数类型检查)
type Constructor<T> = new (...args: any[]) => T
T参数的构造函数
ExtensionMethods (Koishi.js 数据库方法拓展)
type ExtensionMethods<T> = Methods<Database, T extends Constructor<infer I> ? I : never>
若T为构造函数,则置Database类中方法thisArg为T构造出的对象I,否则thisArg为never(即不存在这种情况,该情况下无法使用this)
Get (获取对象中成员)
type Get<T extends {}, K> = K extends keyof T ? T[K] : never
T类中若存在K则返回T[K]类型,若不存在则never
语义:如果能从T成员中找到K则返回T[K]类型
Extract (TypeScript buildin)
export type Extract<S, T, U = S> = S extends T ? U : never
若S为T子类,则返回U
语义:如果能从T中提取S则返回U
RemoveTail (Express 路由路径Split)
type RemoveTail<S extends string, Tail extends string> = S extends `${infer P}${Tail}` ? P : S;
S 若尾部包含 Tail字符串,则推断并返回去除字符串尾部Tail子串的字符串,否则返回原S
语义:将S尾部Tail子串尝试去除
实例:
let correctfoo:RemoveTail<"foo;",";">="foo" let errorfoo:RemoveTail<"foo;",";">="foooo"//Type '"foooo"' is not assignable to type '"foo"'.
GetRouteParameter (Express 路由path顶级目录获取)
type GetRouteParameter<S extends string> = RemoveTail< RemoveTail<RemoveTail<S, `/${string}`>, `-${string}`>, `.${string}` >;
删除S字符串中第一次出现”/” “-” “.”开始的子串。
语义:删除字符串S中”/” “-” “.”及其之后的字符串
RouteParameter (Express 路由path参数提取)
export interface ParamsDictionary { [key: string]: string; } export type RouteParameters<Route extends string> = string extends Route//Exclude null ? ParamsDictionary//Route=null => Dictionary : Route extends `${string}(${string}`//Exclude "(" => contain alternative character in sub path ? ParamsDictionary //Route contain "(" => Dictionary : Route extends `${string}:${infer Rest}`//Contain parameter prefix ":" ? ( GetRouteParameter<Rest> extends never//I Don't know.I think GetRouteParameter always return string ? ParamsDictionary//Dictionary : GetRouteParameter<Rest> extends `${infer ParamName}?`//Contain Alternative Parameter ? { [P in ParamName]?: string }//Contain Alternative Prameter ".../:PARAMETER_NAME?/...." : { [P in GetRouteParameter<Rest>]: string }//Fill parameter in res type ) &//Union with unknown or sub route's params. (Rest extends `${GetRouteParameter<Rest>}${infer Next}`//Get sub route path for process recursively ? RouteParameters<Next> : unknown)//Have Next=>process recursively.No Next=>return unknown : {};//No parameter=>return {}
- Route为字符串
- Route为null
- true:返回 键值对
- false: Route包含”(“
- true:返回 键值对
- false: Route包含“:” 并 推断”:”之后字符串为Rest
- true:Rest中路由参数存在?(不确定,可能TypeScript版本不同,GetRouteParameter总是返回string)
- true:返回 键值对
- false: Rest中变量为可选参数并推断参数名
- true:用参数名构建可空参数对象{ PARAMETER_NAME?:string}
- false: 用参数名构建必填参数对象{ PARAMETER_NAME:string}
- &true: Rest 中包含子路由 “parameter_name/xxxxxxxxxx”=>“/xxxxxx”为子路径
- true:递归获取RouteParameter<Rest>
- false: 返回unknown
- false: 返回 {}
- true:Rest中路由参数存在?(不确定,可能TypeScript版本不同,GetRouteParameter总是返回string)
方法签名变更
type asyncMethod<T, U> = (input: Promise<T>): Promise<Action<U>>; type syncMethod<T, U> = (action: Action<T>): Action<U>; 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" }; } }
假设有一个叫 EffectModule
的类,这个对象上的方法只可能有两种类型签名:asyncMethod、syncMethod,同时该对象上还可能包含一些任意非函数属性。
现在有一个叫 connect
的函数,它接受 EffectModule 实例,将它变成另一个对象,这个对象上只有EffectModule 的同名方法,但是方法的类型签名被改变了:
type asyncMethod<T, U> = (input: Promise<T>)=> Promise<Action<U>>;//Convert to CasyncMethod type CasyncMethod<T, U>=(input: T)=> Action<U>; type syncMethod<T, U> = (action: Action<T>)=> Action<U>;//Convert to CsyncMethod type CsyncMethod<T, U>=(action: T)=> Action<U>;
实例:
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 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
替换成题目的解答,让编译能够顺利通过,并且 index.ts
中 connected
的类型与一下类型完全匹配:
type Connected = { delay(input: number): Action<string>; setMessage(action: Date): Action<number>; }
解答:
//use [keyof T] will remove never. type MethodNames<T> = {[key in keyof T]:T[key] extends Function?key:never}[keyof T];//Retrieve MethodName from T //Get Function in T & Convert it to target type. type Convertor<T> = {[K in MethodNames<T>]:MethodConvertor<T[K]>}; //Simple convertor for these two methods. type MethodConvertor<T> = T extends asyncMethod<infer A,infer B>?CasyncMethod<A,B>:T extends syncMethod<infer A,infer B>?CsyncMethod<A,B>:never; //Create connect function type type Connect = <M>(module: M) => Convertor<M>; let connect:Connect; let t = connect(effectModule);//EffectMoudle's Instance mentioned above. //t.delay => delay(input: number): Action<string> //t.setMessage => setMessage(action: Date): Action<number>
思路:
- 提取对象方法名
- 利用方法名获取函数并转换函数标签,而后使用拥有新类型签名的同名方法构建对象。
v1.0 wep 2022/2/16 更新内容