简体   繁体   English

如何写一个抽象的 typescript 泛型类型?

[英]How to write an abstract typescript generic type?

given the following types给定以下类型

interface Base {
  id: string;
}

interface A extends Base {
  propA: string;
}

interface B extends Base {
 propB: string;
}

I would like to express a generic MyGeneric<T> with the following constraints:我想表达一个具有以下约束的通用MyGeneric<T>

  1. T must be an object T必须是 object
  2. T keys must be string T键必须是string
  3. T values must be instanceOf Base (either of type Base , or of a type extending Base) T值必须是instanceOf Base (类型为Base或扩展 Base 的类型)

    (3. was T values must be compatible with Base interface but it has been reworded in order to avoid incomprehension) (3. T值是否必须与Base接口兼容,但已重新措辞以避免误解)

I tried我试过了

interface MyConstraint {
  [key: string]: Base
}

interface MyGeneric<T extends MyConstraint> {
  data: T
}

But in this case, when the user wants to use it, it has 2 drawbacks:但是在这种情况下,当用户想要使用它时,它有两个缺点:

interface userDefinedInterface1 {
  a: A;
  b: B;
}

function foo1(p: MyGeneric<userDefinedInterface1>):void {
  //drawback 1: this throws TS2344:
  //Type 'userDefinedInterface1' does not satisfy the constraint 'userDefinedInterface1'.   //Index signature is missing in type 'userDefinedInterface1'.
}

//to solve drawback 1, the user has to extend MyConstraint
interface userDefinedInterface2 extends MyConstraint {
  a: A;
  b: B;
}

function foo2(p: MyGeneric<userDefinedInterface2>):void {
  //drawback 2: here, ts considers every string property as valid because of [key: string] 
  console.log(p.data.arbitraryKey);//this is valid
}

Is there a way to define interface MyGeneric<T> to respect the 3 mentioned constraints without these 2 drawbacks?有没有办法定义interface MyGeneric<T>以尊重提到的 3 个约束而没有这两个缺点?

There's only one limitation - if you want to add extra key you have to specify them.只有一个限制——如果你想添加额外的密钥,你必须指定它们。

interface Base {
  id: string;
  num: number;
}

interface A extends Base {
  propA: string;
}

interface B extends Base {
 propB: string;
}

type MyGeneric<T extends Base, K extends keyof any = never> = {
  data: T & {
    [key in K | keyof T]: key extends keyof T ? T[key] : string;
  }
}


const test1: MyGeneric<B, 'test'> = {
  data: {
    id: '123',
    num: 123,
    propB: '123',
    test: '123',
  },
};
const test2: MyGeneric<B> = {
  data: {
    id: '123',
    num: 123,
    propB: '123',
    test: '123', // fails, it has to be provided in current TS.
  },
};

Playground 操场

If you want to rely only on keys of T .如果您只想依赖T的键。

then simply use this version:然后只需使用此版本:

type MyGeneric<T extends Base> = {
  data: T & {
    [key in keyof T]: key extends keyof Base ? Base[key] : string;
  }
}

I think this should solve both of your drawbacks:我认为这应该可以解决您的两个缺点:

type MyConstraint<T> = {
  [K in keyof T]: T[K] extends Base ? T[K] : never;
};

interface MyGeneric<T extends MyConstraint<T>> {
  data: T;
}

For the small price of making the MyConstraint generic.以低廉的价格使MyConstraint通用。 Both of your examples should now work plus of course if you did something like:如果你做了类似的事情,你的两个例子现在应该可以工作了,当然还有:

interface UserDefinedInterface3 {
  a: A;
  b: B;
  c: string;
}

type Wrong = MyGeneric<UserDefinedInterface3>;

You would get an error saying that types of property c are not compatible.您会收到一条错误消息,指出属性c的类型不兼容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM