简体   繁体   English

将通用打字稿类型限制为单个字符串文字值,不允许联合

[英]Restrict generic typescript type to single string literal value, disallowing unions

I have a generic entity type, with the generic used to define a field type based on a set of string literals:我有一个泛型实体类型,泛型用于基于一组字符串文字定义字段类型:

type EntityTypes = 'foo' | 'bar' | 'baz';

type EntityMappings = {
  foo: string;
  bar: number;
  baz: Array<string>;
}

type GenericEntity<T extends EntityTypes> = {
  type: T;
  fieldProperty: EntityMappings[T];
}

What I'm trying to do is require all instances of GenericEntity to have a single type field (a string literal) that then defines the type of fieldProperty, eg:我想要做的是要求 GenericEntity 的所有实例都有一个type字段(字符串文字),然后定义 fieldProperty 的类型,例如:

const instance: GenericEntity<'foo'> = {
  type: 'foo',
  fieldProperty: 'hello',
};

const otherInstance: GenericEntity<'baz'> = {
  type: 'baz',
  fieldProperty: ['a', 'b', 'c'],
}

However, because T extends EntityTypes allows for a union of multiple string literal values in EntityTypes, I'm able to do this, which I want to disallow:但是,因为T extends EntityTypes允许在 EntityTypes 中合并多个字符串文字值,所以我能够做到这一点,但我想禁止:

const badInstance: GenericEntity<'foo' | 'baz'> = {
  type: 'baz',
  fieldProperty: 'blah',
};

This compiles because now type is of type 'foo' | 'baz'编译是因为现在type'foo' | 'baz' 'foo' | 'baz' and fieldProperty is of type string | Array<string> 'foo' | 'baz'和 fieldProperty 是string | Array<string> string | Array<string> , but the two fields no longer correspond like I intend them to. string | Array<string> ,但是这两个字段不再像我想要的那样对应。

Is there a way to restrict the generic declaration on GenericEntity further, to only allow a single unique string literal value?有没有办法进一步限制 GenericEntity 上的泛型声明,只允许一个唯一的字符串文字值? Barring that, is there some other way to insist that any instance of GenericEntity has a type field and a fieldProperty field that correspond?除此之外,还有其他方法可以坚持 GenericEntity 的任何实例都具有对应的type字段和fieldProperty字段吗?

There is currently no direct way to restrict a generic type parameter to a single member of a union .目前没有直接的方法将泛型类型参数限制为union的单个成员。 There's an open feature request at microsoft/TypeScript#27808 to support something like T extends oneof EntityTyes , but that's not implemented yet.microsoft/TypeScript#27808有一个开放的功能请求,以支持T extends oneof EntityTyes之类的东西,但这还没有实现。 If you want to see it happen you might visit that issue and give it a 👍, but I don't know it would have much effect.如果您想看到它发生,您可以访问该问题并给它一个👍,但我不知道它会产生多大影响。

That means T extends EntityTypes could allow T to be any subtype of EntityTypes , including the full EntityTypes union.这意味着T extends EntityTypes可以允许T成为EntityTypes的任何子类型,包括完整的EntityTypes联合。 In practice this tends not to be a huge deal, since usually such T does get inferred as a single member (people often call foo("x") or foo("y") instead of foo(Math.random()<0.5?"x":"y") ).在实践中,这往往不是什么大问题,因为通常这样的T确实被推断为单个成员(人们经常调用foo("x")foo("y")而不是foo(Math.random()<0.5?"x":"y") )。 But sometimes it causes problems, especially with example code like yours.但有时它会导致问题,尤其是像你这样的示例代码。


So how can we work around this?那么我们该如何解决这个问题呢? Given your particular example code, I'd say that you want GenericEntity to actually be more like a discriminated union with three members, instead of a generic type.鉴于您的特定示例代码,我想说您希望GenericEntity实际上更像是具有三个成员的可区分联合,而不是泛型类型。 But you can get both, via a distributive object type as coined in microsoft/TypeScript#47109 .但是您可以通过microsoft/TypeScript#47109中创造的分布式对象类型来获得两者。 It looks like this:它看起来像这样:

type GenericEntity<T extends EntityTypes = EntityTypes> = { [U in T]: {
  type: U;
  fieldProperty: EntityMappings[U];
} }[T]

We are taking the type T passed in and mapping over its members, and then indexing into it with T .我们采用传入的类型T映射其成员,然后使用T 对其进行索引 This has no real effect if T is a single string literal, but when it's a union, the result is also a union without any of the undesirable "cross-correlated" terms:如果T是单个字符串文字,这没有实际效果,但是当它是一个联合时,结果也是一个没有任何不良“交叉相关”术语的联合:

type GE = GenericEntity;
/* type GE = {
    type: "foo";
    fieldProperty: string;
} | {
    type: "bar";
    fieldProperty: number;
} | {
    type: "baz";
    fieldProperty: string[];
} */

(I also made a generic parameter default for T so GenericEntity without a type argument is the full union we actually want here.) (我还为T设置了一个通用参数默认值,所以没有类型参数的GenericEntity是我们在这里真正想要的完整联合。)

So what we're doing is: instead of prohibiting unions in T , we are handling them by distributing over them.所以我们正在做的是:我们不是在T禁止联合,而是通过分配它们来处理它们。

Now things will behave as you desire:现在事情会如你所愿:

const instance: GenericEntity<'foo'> = {
  type: 'foo',
  fieldProperty: 'hello',
} // okay;

const otherInstance: GenericEntity<'baz'> = {
  type: 'baz',
  fieldProperty: ['a', 'b', 'c'],
} // okay

const badInstance: GenericEntity<'foo' | 'baz'> = {
  type: 'baz',
  fieldProperty: 'blah',
}; // error!

Looks good!看起来不错!

Playground link to code Playground 代码链接

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

相关问题 Typescript:是否可以将泛型类型评估为字符串文字? - Typescript: Is it possible to evaluate generic type into string literal? Typescript 字符串字面量 通用类型 function - Typescript String Literal Type in generic function 在TypeScript中,是否可以从字符串的输入类型推断出区分联合的字符串文字类型? - In TypeScript is it possible to infer string literal types for Discriminated Unions from input type of string? Typescript 文字联合 - Typescript literal unions 打字稿将泛型约束为字符串文字类型以用于计算对象属性 - Typescript constrain generic to string literal type for use in computed object property Typescript,基于字符串文字类型值的条件类型 - Typescript, conditional type based on the value of a string literal type 获取typescript泛型类型作为字符串值 - Get typescript generic type as string value 打字稿:一般类型被推断为文字类型 - Typescript: Generic Type being inferred to Literal Type Typescript:通过检查字符串值推断通用类型 - Typescript: Infer generic type by checking string value 目前是否有将两个或多个字符串文字类型连接到 TypeScript 中的单个字符串文字类型的方法? - Is there currently anyway to concatenate two or more string literal types to a single string literal type in TypeScript right now?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM