Машинопись нового класса по оценке параметров
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 ответа
Смотрите ниже для 3.0 ответа
Вы можете использовать аналогичный подход к этому ответу. Вам нужно будет заменить возвращаемый тип конструктора и использовать сопоставленный тип для создания новых функций, в которых пропущен первый аргумент:
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 аргументов, которых должно быть достаточно, но можно добавить еще.
редактировать
Поскольку на первоначальный вопрос был дан ответ, машинопись улучшила возможное решение этой проблемы. С добавлением кортежей в параметрах отдыха и выражениях распределений нам теперь не нужно иметь все перегрузки для RemoveArg
а также ReplaceInstanceType
:
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;
}
Это не только короче, но и решает ряд проблем
- Необязательные параметры остаются необязательными
- Имена аргументов сохраняются
- Работает для любого количества аргументов
Если по какой-то причине вы действительно пытаетесь реализовать это, вы можете сделать что-то вроде следующего. Обратите внимание, что я собираюсь заменить методы только двумя аргументами. Если вам нужно использовать все методы, то ввод текста должен быть более сложным, как в ответе @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);
Вероятно, когда речь заходит об играх с подобными классами, возможны всевозможные предостережения, поэтому будьте осторожны, чтобы вы действительно поняли свой вариант использования и то, что происходит, когда сталкиваетесь с поведением, которое ему не соответствует.
Надеюсь, это поможет. Удачи!