I'm trying to write a generic method that takes any number of arguments that are keys to an object and use the values of their keys as arguments to a constructor. This is my original implementation:
// Typescript 2.x
export function oldMethod<TProps>() {
function create<
TInstance extends Geometry | BufferGeometry,
>(
geometryClass: new () => TInstance,
): any;
function create<
TInstance extends Geometry | BufferGeometry,
TKey1 extends Extract<keyof TProps, string>,
>(
geometryClass: new (param1: TProps[TKey1]) => TInstance,
key1: TKey1,
): any;
function create<
TInstance extends Geometry | BufferGeometry,
TKey1 extends Extract<keyof TProps, string>,
TKey2 extends Extract<keyof TProps, string>,
>(
geometryClass: new (param1: TProps[TKey1], param2: TProps[TKey2]) => TInstance,
key1: TKey1,
key2: TKey2,
): any;
function create<
TInstance extends Geometry | BufferGeometry,
TKey1 extends Extract<keyof TProps, string>,
TKey2 extends Extract<keyof TProps, string>,
TKey3 extends Extract<keyof TProps, string>,
>(
geometryClass: new (param1: TProps[TKey1], param2: TProps[TKey2], param3: TProps[TKey3]) => TInstance,
key1: TKey1,
key2: TKey2,
key3: TKey3,
): any;
// ...all the way up to 8 possible keys
function create<TInstance extends Geometry | BufferGeometry>(
geometryClass: new (...args: Array<TProps[Extract<keyof TProps, string>]>) => TInstance,
...args: Array<Extract<keyof TProps, string>>) {
class GeneratedGeometryWrapper extends GeometryWrapperBase<TProps, TInstance> {
protected constructGeometry(props: TProps): TInstance {
return new geometryClass(...args.map((arg) => props[arg]));
}
}
return class GeneratedGeometryDescriptor extends WrappedEntityDescriptor<GeneratedGeometryWrapper,
TProps,
TInstance,
GeometryContainerType> {
constructor() {
super(GeneratedGeometryWrapper, geometryClass);
this.hasRemountProps(...args);
}
};
}
return create;
}
With the announcement of extracting and spreading parameter lists with tuples in TypeScript 3.0, I was hoping I would be able to remove the overloads to make it a lot simpler as such:
// Typescript 3.x
export function newMethod<TProps>() {
function create<TInstance extends Geometry | BufferGeometry, TArgs extends Array<Extract<keyof TProps, string>>>(
geometryClass: new (...args: /* what goes here? */) => TInstance,
...args: Array<Extract<keyof TProps, string>>) {
class GeneratedGeometryWrapper extends GeometryWrapperBase<TProps, TInstance> {
protected constructGeometry(props: TProps): TInstance {
return new geometryClass(...args.map((arg) => props[arg]));
}
}
return class GeneratedGeometryDescriptor extends WrappedEntityDescriptor<GeneratedGeometryWrapper,
TProps,
TInstance,
GeometryContainerType> {
constructor() {
super(GeneratedGeometryWrapper, geometryClass);
this.hasRemountProps(...args);
}
};
}
return create;
}
However, I don't know what to put as the type for the args
that define the type of the constructor. If I was able to manipulate types like I can objects in JavaScript, I would write it as such: ...[...TArgs].map(TArg => TProps[TArg]
, but obviously that's not valid TypeScript syntax and I can't think of any way to do it. Am I missing a way to express this type? Is there some way of making this completely type-safe without having to have the function overloads and a finite number of arguments? Is there some TypeScript feature that's missing that would allow me to express this type?