在TypeScript中映射通用的spreaded参数类型

Nat*_*ema 8 generic-collections typescript

我正在尝试编写一个泛型方法,该方法接受任意数量的参数作为对象的键,并使用其键的值作为构造函数的参数.这是我最初的实现:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

随着在TypeScript 3.0中使用元组提取和传播参数列表的宣布,我希望我能够删除重载,使其更加简单:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

但是,我不知道要把什么作为args定义构造函数类型的类型.如果我能够在JavaScript中操作像我可以对象这样的类型,我会这样写:...[...TArgs].map(TArg => TProps[TArg]但显然这不是有效的TypeScript语法,我想不出任何方法来做到这一点.我错过了表达这种类型的方法吗?有没有办法使这个完全类型安全,而不必有函数重载和有限数量的参数?是否有一些缺少的TypeScript功能可以让我表达这种类型?

jca*_*alz 7

我为下面的例子删除了很多代码,但它应该是同样的精神.

您缺少的功能称为映射数组/元组,计划在2018年8月的某个时间在TypeScript 3.1中发布.您将能够像映射其他类型一样映射数组和元组,如下所示:

type Mapped<T> = {[K in keyof T]: Array<T[K]>};
type Example = Mapped<[string, number, boolean]>; 
// type Example = [string[], number[], boolean[]];
Run Code Online (Sandbox Code Playgroud)

如果你typescript@next现在使用,你可以尝试一下.


在你的情况下,你想做的事情是这样的

type MappedArgs = {[K in keyof TArgs]: TProps[TArgs[K]]};
type ConstructorType = new (...args: MappedArgs) => any;
Run Code Online (Sandbox Code Playgroud)

但是有一些突出的问题阻止你这样做.一个是由于某种原因,编译器还不了解这TArgs[K]是一个有效的索引TProps.所以你可以引入一个条件类型,允许你解决这个问题:

type Prop<T, K> = K extends keyof T ? T[K] : never;
type MappedArgs = {[K in keyof TArgs]: Prop<TProps,TArgs[K]>};
Run Code Online (Sandbox Code Playgroud)

但以下仍然不起作用:

type ConstructorType = new (...args: MappedArgs) => any;
// error, a rest parameter must be of an array type
Run Code Online (Sandbox Code Playgroud)

嗯,MappedArgs肯定是一个数组类型,但TypeScript没有意识到它.不能这样说服它:

type MappedArgs = {[K in keyof TArgs]: Prop<TProps,TArgs[K]>} 
  & unknown[]; // definitely an array!

type ConstructorType = new (...args: MappedArgs) => any;
// error, a rest parameter must be of an array type 
Run Code Online (Sandbox Code Playgroud)

这似乎是映射数组/元组中的一个突出错误,其中映射类型在任何地方都不被视为数组.这可能会通过TypeScript 3.1的发布得到解决.现在,您可以通过添加新的虚拟类型参数来执行变通方法,如

type Prop<T, K> = K extends keyof T ? T[K] : never;
type MappedArgs = {[K in keyof TArgs]: Prop<TProps,TArgs[K]>}
  & unknown[]; // definitely an array!
type ConstructorType<A extends MappedArgs = MappedArgs> = new (...args: A) => any;
Run Code Online (Sandbox Code Playgroud)

这很有效.让我们看看我们是否可以测试这个东西.怎么样:

type Prop<T, K> = K extends keyof T ? T[K] : never;
interface NewMethod<TProps> {
  create<TArgs extends Array<Extract<keyof TProps, string>>,
    MTArgs extends unknown[] & { [K in keyof TArgs]: Prop<TProps, TArgs[K]> }>(
      geometryClass: new (...args: MTArgs) => any,
      ...args: Array<Extract<keyof TProps, string>>): void;
}

declare const z: NewMethod<{ a: string, b: number }>;
z.create(null! as new (x: string, y: number) => any, "a", "b"); // okay
z.create(null! as new (x: string, y: number) => any, "a", "c"); // error, "c" is bad
z.create(null! as new (x: string, y: boolean) => any, "a", "b"); // error, constructor is bad
Run Code Online (Sandbox Code Playgroud)

这些似乎充当你想要的方式...虽然在最后一种情况下的错误实在是不起眼,似乎不指出的问题是,类型y参数boolean和不匹配stringnumberTProps[keyof TProps].


无论如何,截至2018年8月,这仍然是最前沿的东西,所以我认为你可能需要等待一段时间才能确定它是如何工作的.希望有所帮助.祝好运!