[英]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!看起来不错!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.