TypeScript类型依赖于属性名?



这是一个有点奇怪的问题,但我仍然很好奇这是可能的。

我有一个来自API的JSON结构,它定义了一个像这样的映射:

"sizes: {
"thumbnail": "xxxx",
"thumbnail-width: 150,
"thumbnail-height: 150,
"medium: "xxx",
"medium-width": 500,
"medium-height": 500,
...
}

名称在某种程度上是预定义的,但不是真正的。

我的问题是,如果有可能定义任何以-width或-height结束的属性,以数字和其他一切作为字符串。就像regex (.*)-(width|height): number.

TypeScript可以做各种神奇的事情,但它能做到像这样吗?

现在,从TS4.1开始,TypeScript中还没有特定的、具体的类型(我们叫它Values)来表示可赋值给values属性的值。为了记录,我将此解释为:任何字符串键都是可以接受的;如果键以-width-height结尾,则属性必须为number;否则必须是string。没有要求这些键必须以三元组的形式出现;例如,如果"key"是一个键,则不需要"key-height""key-width"存在。

也许当microsoft/TypeScript#42192得到解决时,你就可以使用"pattern"表单`${string}-width`的模板字面值(在microsoft/TypeScript#40598中实现)作为键。现在你不能。

UPDATE for TS4.4:即使使用microsoft/TypeScript#44512中实现的模式模板文字索引签名,你也不能为Values创建一个特定的类型。您现在可以表示以-height-width结尾的任意字符串必须为number的部分:

type NumericValues = {
[key: `${string}-${"height" | "width"}`]: number; 
}

但是没有办法表示"其他的都需要是string"约束……看到微软/打印稿# 17867。所以下面的代码不起作用:

type BadValues = {
[key: `${string}-${"height" | "width"}`]: number; // error! incompatible
[key: `${string}`]: string; // can't say "everything else" here
}

哦。


因此,您只能将您的限制表示为通用约束,形式为Values<K>,用于适当的K:

type Values<K extends string> = {
[P in K]: P extends `${string}-${"height" | "width"}` ? number : string
};

如果KValues<K>中字符串值键的集合,则每个属性值必须是numberstring,这取决于K是否以"-height"/"-width"结尾。

对于某些具体的K,您可以使用辅助函数使编译器为您推断K,而不是强制将值注释为Values<K>类型:

const asValues = <K extends string>(values: Values<K>) => values;

让我们试一试:

const sizes = asValues({
"thumbnail": "xxxx",
"thumbnail-width": 150,
"thumbnail-height": 150,
"medium": "xxx",
"medium-width": 500,
"medium-height": 500,
}); // okay
const badSizes = asValues({
"toenail": 123, // error! Type 'number' is not assignable to type 'string'
"toenail-width": 456,
"psychic": "yyy",
"psychic-height": "tall" // Type 'string' is not assignable to type 'number'
})

看起来不错。sizes可以接受,但是badSizes会导致属性类型不正确的错误。


当然,我们使用泛型的事实意味着您必须在想要强制执行此限制的任何地方拖动泛型类型参数K。如果将其用于整个代码库,可能会变得笨拙。相反,你可能只想使用Values<K>来验证传入的对象字面量,然后当你操作它们时,你把它们扩展到TypeScript可以表示的最接近的特定类型,比如string-可索引类型,它的属性是string | number:

type WideValues = { [k: string]: string | number };
const wideValues: WideValues = sizes; // okay

使用Values<K>进行验证,然后使用WideValues进行操作。


Playground链接到代码

从typescript 4.1开始,使用文字类型是可能的。一个例子:

type AddSuffixToEachProps<T, Suffix extends string, Value extends any> = T & {
[P in keyof T as `${string & P}-${Suffix}`]: Value
}

这是一个接受某种模式的类型,并为每个这些道具添加后缀。

用法:

type Result = AddSuffixToEachProps<{
foo: string,
bar: string[]
}, 'width', number>
const test: Result = {
foo: 'some string',
"foo-width": 123, // required
bar: ['one', 'two', 'three'],
"bar-width": 456 // required
}

不,不可能。

因为

索引签名参数类型必须为'string'或'number'。

不能设置${string}-width${string}-height类型作为sizes的键类型。

最新更新