typescript - 通过将键和关联值类型传递给泛型来缩小索引类型
问题描述
与类似问题的差异
不幸的是,对将隐式键和值类型关系传递给 TypeScript 泛型问题的出色答案并未涵盖当前问题中的问题:在该问题generateInputsAccessObject
的目标函数中,我们不使用与子类型相关的属性。
事实上,上面的问题已经解决了,没有将隐式的键和值类型关系传递给 TypeScript 泛型,也就是标题,但我想现在我们不能再避免了。
目标
创建EntitySpecification
, 的泛型类型ProductSpecification
如:
properties
的类型必须是非索引的。这意味着 TypeScript 编译器必须知道存在什么ID
和price
存在,但其他键 - 不存在。properties
必须是可迭代的(Object.entires("ProductSpecification.properties")
必须工作)。ID
andprice
可以有不同的类型,但是当我们调用EntitySpecification.properties.ID
or时EntitySpecification.properties.price
,TypeScript 编译器必须知道是哪种类型。
export type Product = {
ID: string;
price: number;
};
const ProductSpecification: EntitySpecification<keyof Product> = {
name: "Product",
properties: {
ID: {
type: DataTypes.string,
emptyStringIsAllowed: false
},
price: {
type: DataTypes.number,
numberSet: NumbersSets.nonNegativeInteger
}
}
};
这里:
export enum DataTypes {
number = "NUMBER",
string = "STRING"
}
export enum NumbersSets {
naturalNumber = "NATURAL_NUMBER",
nonNegativeInteger = "NON_NEGATIVE_INTEGER",
negativeInteger = "NEGATIVE_INTEGER",
negativeIntegerOrZero = "NEGATIVE_INTEGER_OR_ZERO",
anyInteger = "ANY_INTEGER",
positiveDecimalFraction = "POSITIVE_DECIMAL_FRACTION",
negativeDecimalFraction = "NEGATIVE_DECIMAL_FRACTION",
decimalFractionOfAnySign = "DECIMAL_FRACTION_OF_ANY_SIGN",
anyRealNumber = "ANY_REAL_NUMBER"
}
export type StringSpecification = {
readonly type: DataTypes.string;
readonly emptyStringIsAllowed: boolean;
};
export type NumberSpecification = {
readonly type: DataTypes.number;
readonly numberSet: NumbersSets;
};
当然,EntitySpecification
预先不知道密钥,但是密钥计数可能是任意大的(不是当前的 2 个ProductSpecification
)。
现在最好的我
以下解决方案满足前两个目标:
export type EntitySpecification<Keys extends string> = {
readonly name: string;
readonly properties: { [key in Keys]: StringSpecification | NumberSpecification };
};
这里的冲突情况,违反第三个条件的后果:
type StringValidationRules = {
emptyStringIsAllowed: boolean;
};
const ID_ValidationRules: StringValidationRules = {
emptyStringIsAllowed: ProductSpecification__EXPERIMENTAL.properties.ID.emptyStringIsAllowed;
}
因为 TypeScript 不知道ID
有StringSpecification
类型,所以我们有以下错误:
TS2339: Property 'emptyStringIsAllowed' does not exist on type 'StringSpecification | NumberSpecification'. Property 'emptyStringIsAllowed' does not exist on type 'NumberSpecification'.
解决方案
看起来您EntitySpecification
只映射对象类型的属性键类型,而您还需要映射相应的值类型。在这种情况下,最好传入整个对象类型而不仅仅是其键。然后,您可以通过一些映射结构映射属性类型,同时像现在一样继续映射键。
这是表示必要映射结构的一种方法:
type DataMapping =
{ type: number, specification: NumberSpecification }
| { type: string, specification: StringSpecification }
请注意,我不打算在任何地方使用type的实际值。DataMapping
它只是一个类型定义,编译器将能够使用它来将属性类型连接到规范接口。
现在EntitySpecification
可以定义如下:
type EntitySpecification<T extends Record<keyof T, DataMapping["type"]>> = {
readonly name: string;
readonly properties: { [K in keyof T]: Extract<DataMapping, { type: T[K] }>['specification'] };
};
请注意,我被限制 为一个对象类型,其属性T
类型在. 这将允许其属性类型仅为and ,但不允许像因为没有(当前)有相应的规范。type
DataMapping
Product
number
string
{oops: boolean}
boolean
该properties
属性是一个映射类型,其中的键K
与 的键相同T
(就像您之前所做的那样),并且其值是从 的相应属性类型转换而来的T
,即T[K]
.
具体的属性映射是Extract<DataMapping, {type: T[K]}>['specification']
,使用Extract
实用程序类型选择属性为的DataMapping
联合体的成员,然后返回该成员的属性。type
T[K]
specification
让我们看看它是如何工作的Product
:
type Product = {
ID: string;
price: number;
};
type ProductSpecification = EntitySpecification<Product>;
/* type ProductSpecification = {
readonly name: string;
readonly properties: {
ID: StringSpecification;
price: NumberSpecification;
};
} */
这就是你想要的,我想。如果你有一个p
类型的值ProductSpecification
,那么p.properties.ID
必须是类型的StringSpecification
,而且p.properties.price
必须是类型的NumberSpecification
。万岁!
推荐阅读
- c - 循环后如何访问C中结构的第一个元素
- airflow - 气流日志文件权限被拒绝
- vue.js - Firebase 在日期范围内获取客人
- powershell - 更好地理解 Powershell 中的点符号
- flutter - 如何使用 DateTime 作为时间间隔?
- javascript - 如何尽可能快地运行多个异步函数(JS)?
- python - gpflow分类实现
- azure-powershell - 在 Azure 管道中,Powershell 如何使任务失败?
- excel - 除了office365 excel之外,还有什么FILTER功能的替代品?
- linker - SDCC Z80:避免地址