Typescript学习

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: 返回 {}

方法签名变更

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 更新内容

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注