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

时间:2018-08-08 00:20:54

标签: typescript generic-collections

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

// 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;
}

随着TypeScript 3.0中使用元组提取和扩展参数列表的公告,我希望能够删除重载使其变得更加简单:

args

但是,我不知道在定义构造函数类型的...[...TArgs].map(TArg => TProps[TArg]的类型中放置什么。如果我能够像使用JavaScript中的对象一样操作类型,则可以这样写:use \google\appengine\api\cloud_storage\CloudStorageTools; // ... $path = "gs://my-app.appspot.com/the_image_name.jpg" $imageUrl = CloudStorageTools::getImageServingUrl($path, ['secure_url' => true, 'size' => 0]); header("Location: $imageUrl"); ,但是显然这不是有效的TypeScript语法,而且我想不出任何办法。我是否缺少表达这种类型的方法?是否有某种方法可以使此类型完全安全,而不必具有函数重载和有限数量的参数?是否有一些TypeScript功能缺失,可以让我表达这种类型?

1 个答案:

答案 0 :(得分:7)

在下面的示例中,我删除了很多代码,但是应该保持一致。

您缺少的功能称为mapped arrays/tuples,该功能计划于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[]];

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


对于您而言,您想要 做的事情类似

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

但是有一些突出的问题阻止您这样做。一个是由于某种原因,编译器尚未了解TArgs[K]TProps的有效索引。因此,您可以引入一个conditional type来解决该问题:

type Prop<T, K> = K extends keyof T ? T[K] : never;
type MappedArgs = {[K in keyof TArgs]: Prop<TProps,TArgs[K]>};

但是以下内容仍然无效:

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

嗯,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 

这似乎是映射数组/元组中的outstanding bug,其中映射类型并不是到处都看做数组。 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;

,并且有效。让我们看看是否可以测试这个东西。怎么样:

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

这些似乎以您想要的方式运行...尽管在最后一种情况下的错误确实很模糊,并且似乎并未指出问题在于y参数的类型为{{ 1}},并且与boolean中的stringnumber不匹配。


无论如何,截至2018年8月,这仍然是最前沿的内容,因此,我认为您可能需要稍等片刻,然后才能确定它的工作原理。希望能有所帮助。祝你好运!