简体   繁体   English

Typescript-使用泛型扩展具有联合类型属性的接口

[英]Typescript - Using generics to extend an interface with a property of union type

I have an interface like this 我有这样的界面

interface Test {
  values: string[] | number[] | Date[]
  // more properties
}

I would like to create another interface that makes the type specific. 我想创建另一个使类型特定的接口。 What I am trying is: 我正在尝试的是:

interface TestWithType<T extends string | number | Date> extends Test {
  values: T[];
}

But this fails with an error saying Type 'string' is not assignable to type 'Date' . 但这失败并显示一条错误消息,提示无法将Type 'string' is not assignable to type 'Date'

What is the correct syntax for this? 正确的语法是什么? If property values was not an array, this would work fine 如果属性values不是数组,则可以正常工作

interface Test {
  values: string | number | Date
}

interface TestWithType<T extends string | number | Date> extends Test {
  values: T;
}

Two possible solutions, using lookup types or conditional types: 使用查找类型或条件类型的两种可能的解决方案:

enum TestType { STRING, NUMBER, DATE }
interface TestTypeMapping {
  [TestType.STRING]: string[];
  [TestType.NUMBER]: number[];
  [TestType.DATE]: Date[];
}
interface TestWithType1<T extends TestType> extends Test {
  values: TestTypeMapping[T];
}

type TypeToArrayType<T> =
  T extends string ? string[] :
  T extends number ? number[] :
  T extends Date ? Date[] :
  never[];
interface TestWithType2<T extends string | number | Date> extends Test {
  values: TypeToArrayType<T>;
}

As you've noticed, you don't get string[] | number[] | Date[] 如您所知,您不会得到string[] | number[] | Date[] string[] | number[] | Date[] string[] | number[] | Date[] by apply making an array of elements of type string | number | Date string[] | number[] | Date[]通过应用使string | number | Date类型的元素数组string | number | Date string | number | Date string | number | Date . string | number | Date And since (string | number | Date)[] is wider than string[] | number[] | Date[] 并且由于(string | number | Date)[]string[] | number[] | Date[] string[] | number[] | Date[] string[] | number[] | Date[] , you get a compiler error since you can't widen properties of subtypes. string[] | number[] | Date[] ,由于无法扩展子类型的属性,因此会出现编译器错误。

@MattMcCutchen's answer gives some ways to deal with this. @MattMcCutchen的答案提供了一些解决方法。 Here's another way: 这是另一种方式:

If you have a union like string | number | Date 如果您有类似string | number | Date的联合string | number | Date string | number | Date string | number | Date and want to programmatically turn it into a union of arrays like string[] | number[] | Date[] string | number | Date并希望以编程方式将其转换为诸如string[] | number[] | Date[]的数组的并集 string[] | number[] | Date[] string[] | number[] | Date[] , you can use distributive conditional types : string[] | number[] | Date[] ,可以使用分布式条件类型

type DistributeArray<T> = T extends any ? T[] : never;

Then you can define TestWithType in terms of DistributeArray : 然后,您可以根据DistributeArray定义TestWithType

// no error:
interface TestWithType<T extends string | number | Date> extends Test {
  values: DistributeArray<T>;
}

And verify that it behaves as you expect: 并验证其行为是否符合您的预期:

declare const testWithString: TestWithType<string>
testWithString.values; // string[]

declare const testWithDate: TestWithType<Date>
testWithDate.values; // Date[]

declare const testWithStringOrNumber: TestWithType<string | number>
testWithStringOrNumber.values; // string[] | number[]

Hope that helps. 希望能有所帮助。 Good luck! 祝好运!


EDIT: 编辑:

As a related question, is there any way to forbid passing an union type to the generic? 作为一个相关问题,是否有任何方法可以禁止将联合类型传递给泛型? (As in requiring to only specify at most one of string, number or date) (如要求仅指定字符串,数字或日期之一)

Yeah, that's possible , but it requires abusing the type system in a way that makes me uncomfortable. 是的,这是可能的 ,但是它需要以一种使我不舒服的方式滥用类型系统。 I'd suggest not doing that if you don't need to. 如果您不需要,我建议不要这样做。 Here it is: 这里是:

type DistributeArray<T> = T extends any ? T[] : never;
type NotAUnion<T> = [T] extends [infer U] ? U extends any ? 
  T extends U ? unknown : never : never : never
type ErrorMsg = "NO UNIONS ALLOWED, PAL"
interface TestWithType<T extends (
  unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg
)> extends Test {
  values: DistributeArray<T>
}

declare const testWithString: TestWithType<string> // okay

declare const testWithDate: TestWithType<Date> // okay

declare const testWithStringOrNumber: TestWithType<string | number> // error:
// 'string | number' does not satisfy the constraint '"NO UNIONS ALLOWED, PAL"'.

The type NotAUnion<T> evaluates to unknown (a top type) if T is not a union... otherwise it evaluates to never (a bottom type). 如果T不是一个并集,则类型NotAUnion<T>计算结果为unknown (顶部类型)...否则,其计算结果为never (底部类型)。 Then, in TestWithType the type T is constrained to unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg 然后,在TestWithType将类型T约束为unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg , barely skirting the rules against circular references. unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg ,几乎没有对循环引用使用规则。 If you pass a non-union as T , it becomes T extends string | number | Date 如果将非工会传递为T ,则它变为T extends string | number | Date T extends string | number | Date T extends string | number | Date as before. T extends string | number | Date和以前一样。 If you pass a union, it becomes T extends ErrorMsg , a string literal type. 如果传递并集,则它将成为T extends ErrorMsg ,它是字符串文字类型。 Since a union will never extend a string literal, it will fail... And the error message you get will involve ErrorMsg , so it should hopefully be enough for a developer to realize what's happening. 由于联合永远不会扩展字符串文字,因此它将失败...并且您收到的错误消息将涉及ErrorMsg ,因此对开发人员ErrorMsg ,应该足以意识到正在发生的事情。 It works, but it's very very sketchy stuff. 它可以工作,但是非常粗略。 You asked for it, though. 但是,您要了。

Good luck again! 再次祝你好运!

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

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