通过评估params打字稿新类

时间:2018-04-30 14:29:53

标签: class typescript decorator evaluate

class A {
   public aCall(a: any, payload: string) {}
   public bCall(a: any, payload: number) {}
   public cCall(a: any) {}
   .
   .
   .
}

function createNewClass(aCtor: A) {
  // load all of the A method and remove first params
  // generic code on here
  // Final result should be like this
  return class B {
    public aCall(payload: string) {}
    public bCall(payload: number) {}
  }
}

// C.d.ts
interface C extends createNewClass(A) {}

我是否可以使用函数(或方法上的装饰器)来评估传入的类并生成新类,删除所有第一个参数,以便我可以使用新类进行扩展,或者它只是无法执行此操作

2 个答案:

答案 0 :(得分:1)

请参阅下面的3.0回答

您可以对此answer使用类似的方法。您将需要替换构造函数的返回类型,并使用映射类型来创建省略第一个参数的新函数:

type RemoveFirstArg<TCtor extends new (... args: any[]) => any > = ReplaceInstanceType<TCtor,  { [P in keyof InstanceType<TCtor>]: RemoveArg<InstanceType<TCtor>[P]> }>
function createNewClass<TCtor extends new (... args: any[]) => any >(aCtor: TCtor) : RemoveFirstArg<TCtor>{
    // load all of the A method and remove first params
    return null as any;
}

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type RemoveArg<T> = T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
    IsValidArg<J> extends true ? (b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => R :
    IsValidArg<I> extends true ? (b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => R :
    IsValidArg<H> extends true ? (b: B, c: C, d: D, e: E, f: F, g: G, h: H) => R :
    IsValidArg<G> extends true ? (b: B, c: C, d: D, e: E, f: F, g: G) => R :
    IsValidArg<F> extends true ? (b: B, c: C, d: D, e: E, f: F) => R :
    IsValidArg<E> extends true ? (b: B, c: C, d: D, e: E) => R :
    IsValidArg<D> extends true ? (b: B, c: C, d: D) => R :
    IsValidArg<C> extends true ? (b: B, c: C) => R :
    IsValidArg<B> extends true ? (b: B) => R :
    IsValidArg<A> extends true ? () => R :
    T
) : never

type ReplaceInstanceType<T, TNewReturn> = T extends new (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
    IsValidArg<J> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => TNewReturn :
    IsValidArg<I> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => TNewReturn :
    IsValidArg<H> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => TNewReturn :
    IsValidArg<G> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => TNewReturn :
    IsValidArg<F> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F) => TNewReturn :
    IsValidArg<E> extends true ? new (a: A, b: B, c: C, d: D, e: E) => TNewReturn :
    IsValidArg<D> extends true ? new (a: A, b: B, c: C, d: D) => TNewReturn :
    IsValidArg<C> extends true ? new (a: A, b: B, c: C) => TNewReturn :
    IsValidArg<B> extends true ? new (a: A, b: B) => TNewReturn :
    IsValidArg<A> extends true ? new (a: A) => TNewReturn :
    new () => TNewReturn
) : never

//Usage
class A {
    public aCall(a: any, payload: string) { }
    public bCall(a: any, payload: number) { }
}

// Extending a class
class C extends createNewClass(A) { }

new C().aCall('xxx')

//For interfaces we can just use the type
interface IC extends RemoveFirstArg<typeof A> { }

注意 许多类似行的原因是我们需要使用特定数量的参数重新映射每个构造函数/函数。上面的实现适用于10个参数,这些参数应该足够但可以添加更多参数。

修改

由于原始问题得到解答,打字稿已经改进了这个问题的可能解决方案。添加Tuples in rest parameters and spread expressions后,我们现在不需要为RemoveArgReplaceInstanceType设置所有重载:

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;

type ArgumentTypes<T> = T extends (... args: infer U ) => any ? U: never;
type ReplaceInstanceType<T, TNewInstance> = T extends new (...args: any[])=> infer R ? new (...a: ArgumentTypes<T>) => TNewInstance : never;

type ArgumentTypesSkipOne<T> = T extends (a: any, ... args: infer U ) => any ? U: never;
type RemoveArg<T> = T extends (a: any, ...args: any[])=> infer R ? (...a: ArgumentTypesSkipOne<T>) => R : T;

type RemoveFirstArg<TCtor extends new (... args: any[]) => any > = ReplaceInstanceType<TCtor,  { [P in keyof InstanceType<TCtor>]: RemoveArg<InstanceType<TCtor>[P]> }>

function createNewClass<TCtor extends new (... args: any[]) => any >(aCtor: TCtor) : RemoveFirstArg<TCtor>{
    // load all of the A method and remove first params
    return null as any;
}

这不仅缩短了,而且解决了许多问题

  • 可选参数保持可选
  • 保留参数名称
  • 适用于任意数量的论点

答案 1 :(得分:1)

如果由于某种原因,你关心实际尝试实现这个东西,你可以做类似以下的事情。请注意,我只会用两个参数替换方法。如果你需要做所有的方法,打字必须更加精细,如@ TitianCernicova-Dragomir的回答:

type RemoveFirstArgOfTwoArgMethods<T> = { [K in keyof T]:
  T[K] extends (a: any, payload: infer P) => infer R ? (payload: P) => R : T[K];
}

function createNewClass<T>(aCtor: new (...args: any[]) => T): new (...args: any[]) => RemoveFirstArgOfTwoArgMethods<T> {

  const B = (class extends (aCtor as any) {}) as new (...args: any[]) => RemoveFirstArgOfTwoArgMethods<T>;

  // you will need to actually decide what that first argument will be
  const firstVal: any = "whoKnows";

  Object.keys(aCtor.prototype).forEach(k => {
    const protoVal = (aCtor.prototype)[k];
    if ((typeof protoVal === 'function') && (protoVal.length === 2)) {
      B.prototype[k] = function (...args: any[]) { return (protoVal as Function).call(this, firstVal, ...args) }
    }
  })

  return B;
}

这个想法是它将扩展原始类,但用新的单参数方法替换它的两个参数方法,这些方法使用常量第一个参数调用原始方法(在这种情况下,它是字符串"whoKnows"但是你可能想要别的东西。)

您可以验证以上是否有效:

class A {
  public aCall(a: any, payload: string) {
    console.log("aCall(" + a + "," + payload + ")");
  }
}

const a = new A();
a.aCall("explicit", "call"); // aCall(explicit, call);

const C = createNewClass(A);
const c = new C();
c.aCall("implicit"); // aCall(whoKnows, implicit);

在使用这样的类玩游戏时可能会有各种各样的注意事项,所以要小心你真正理解你的用例以及面对不符合它的行为时会发生什么。

希望有所帮助。祝你好运!