typescript - 打字稿通用联合解析顺序
问题描述
我正在尝试使用字符串 Union 作为类型参数创建一个接口。它有两个孩子,都与联盟有关。这个想法是它们通过类型参数相互协调。
这是一个简单的复制:
interface IOptions<TValue extends string> {
values: {[key in TValue]: string};
items: IItem<TValue>[];
}
interface IItem<TValue extends string> {
value: TValue;
}
function printOptions<TValue extends string>(options: IOptions<TValue>) {
console.log(options);
}
printOptions({
values: {a: 'a', b: 'b'},
items: [
{ value: 'a'}
]
});
此代码产生以下错误:
Type '{ a: string; b: string; }' is not assignable to type '{ a: string; }'.
我对发生的事情的猜测是,打字稿决定该items
数组将是定义通用 Union 是什么的孩子,然后抱怨values
具有未知属性。
我的问题是,我可以以某种方式将打字稿values
用作“联合定义器”类型吗?或者以其他方式使其values
具有不出现在中的键items
?
解决方案
您只需要推断参数的每个键和值。考虑这个例子:
interface Option<Values, Items> {
values: Values;
items: Items;
}
type Item<Value extends PropertyKey> = { value: Value }
function printOptions<
Value extends string,
Values extends Record<Value, Value>,
InferedItem extends Item<keyof Values>,
Items extends InferedItem[]
>(options: Option<Values, Items>) {
}
printOptions({
values: { a: 'a', b: 'b' },
items: [
{ value: 'a' }
]
});
printOptions({
values: { a: 'a', b: 'b' },
items: [
{ value: 'c' } // error
]
});
将其视为类型分解。如果你想推断每个值,你应该解构它。换句话说,从下到上创建结果类型。
首先,您需要推断出
key/value
属性values
:Value extends string
然后,您需要推断整个
values
属性:Values extends Record<Value, Value>
与项目相同的方法。您需要推断一项:
InferedItem extends Item<keyof Values>
然后您可以推断所有项目:
Items extends InferedItem[]
有兴趣Type Inference on function arguments
可以看看我的文章
如果要禁止values
在数组中使用键items
,则需要提供验证类型助手。您可以在我的文章/博客中找到更多解释和示例。
interface Option<Values, Items> {
values: Values;
items: Items;
}
type Item<Value extends PropertyKey> = { value: Value }
type Check<
Values extends Record<string, string>,
Items extends Array<any>,
Cache extends Array<any> = []
> =
Items extends []
? Cache
: Items extends [infer Head, ...infer Tail]
? Head extends Item<infer Value>
? Value extends keyof Values
? Check<Values, Tail, [...Cache, Item<never>]>
: Check<Values, Tail, [...Cache, Item<Value>]>
: 1
: Items;
function printOptions<
Value extends string,
ItemValue extends string,
Values extends Record<Value, Value>,
InferedItem extends Item<ItemValue>,
Items extends InferedItem[],
>(options: Option<Values, Check<Values, [...Items]>>) {
}
printOptions({
values: { a: 'a', b: 'b' },
items: [
{ value: 'c' }, // ok
{ value: 'a' }, // error
]
});
验证算法:遍历items
元组并检查每个value
它是否从values
. 如果是 - 替换value:char
为value:never
,否则不要使用item
.
Check
返回经过验证items
的元组,用作 的第二个参数Options
。现在,我们最终得到了一个元组,其中每个无效value
都被替换为never
. SIncenever
是不可表示的,它被 TS 编译器突出显示。
推荐阅读
- mysql - MySQL - 具有非聚合列的 COUNT()
- javascript - 如何将数据添加到 React 的 props
- python - 向字典中添加多个键(python)
- c# - 使用非托管导出将字符串从 C# 返回到 C++ 返回数字
- c++ - 文件存在时,最新检查声称构建输入“丢失”
- opentk - OpenTK 识别 PS4v2 控制器(通过 USB)
- machine-learning - 创建基于 3 列的预测模型
- android - IntentService 使用 NativeScript 和 Angular 处理 FCM
- r - ggplot2中的分组条形图
- qt - 仅从 Qt 中的给定 MIME 类型(无文件本身)获取文件图标