如何键入具有相应类型的元组数组?
Posted
技术标签:
【中文标题】如何键入具有相应类型的元组数组?【英文标题】:How to type tuple array with corresponding types? 【发布时间】:2022-01-06 03:31:49 【问题描述】:谁能帮我解决这个不寻常(我认为)的问题?
最初,我正在实现类型化 Map。
export enum KeyType
aa = 'aa',
bb = 'bb'
export interface ValueTypes
aa: boolean,
bb: string
interface TypedMap extends Map<KeyType, ValueTypes[KeyType]>
get<TK extends KeyType>(k: TK): ValueTypes[TK] | undefined
set<TK extends KeyType>(k: TK, v: ValueTypes[TK]): this
上面的代码运行良好。然后我想实现一个能够为这个地图设置多个值的函数:
function setMany<TKey, TVal> (
map: Map<TKey, TVal>,
change: (
Map<TKey, TVal> |
[TKey, TVal][]
)
): void
const entries = change instanceof Map ? change.entries() : change
for (const [key, value] of entries)
map.set(key, value)
如何输入[TKey, TVal][]
元组数组,以便对输入进行相应的类型检查?喜欢:
const tm = new Map() as TypedMap
setMany(
tm,
[
[KeyType.aa, 'Foo'], // Error, aa requires boolean
[KeyType.bb, 'Bar'] // Nice, bb requires string
]
)
据我了解,我需要这样的东西:
type ChangeTuple<TK extends KeyType> = [TK, ValueTypes[TK]]
但要使用参数和数组。我试过这个:
interface setManyV2
(
map: TypedMap,
change: (
TypedMap |
ChangeTuple[] // requires Generic type, but ChangeTuple<KeyType>[] doesn't work because TK doesn't extend anymore
)
): void
测试用例:
setMany(
tm,
[
[KeyType.aa, true], // correct, aa requires boolean
[KeyType.aa, false], // correct, aa requires boolean
[KeyType.bb, 'any string'], // correct bb requires string
[KeyType.aa, undefined], // incorrect, aa requires boolean, not undefined
[KeyType.aa, new Set()], // incorrect, aa requires boolean, not Set
[KeyType.aa, 'any string'], // incorrect, aa requires boolean, not string
[KeyType.bb, new Set()], // incorrect, aa requires string, not Set
[KeyType.bb, undefined], // incorrect, aa requires string, not undefined
[KeyType.bb, false], // incorrect, aa requires string, not boolean
[KeyType.bb, true] // incorrect, aa requires string, not boolean
]
)
const tm2 = new Map() as TypedMap
tm2.set(KeyType.aa, true)
tm2.set(KeyType.bb, 'string')
setMany(
tm,
tm2
)
【问题讨论】:
您的第一个示例导致错误 @captain-yossarian 谢谢,已修复 如果在setManyV2
接口中使用ChangeTuple<KeyType>[]
会怎样?
@Dahou 然后它将接受[KeyType.aa | KeyType.bb, boolean | string]
用于所有不可接受的元组 - 我需要[KeyType.aa, boolean]
或[KeyType.bb, string]
之一。
【参考方案1】:
我假设您希望通过TypedMap
重载您的Map
实现。如果是,有更好的方法。
为了重载Map
,您需要与Map
类型相交,如下所示:Map<'a', boolean> & Map<'b', string>
。
既然你有KeyType
和ValueTypes
,我们可以把它们合并成一个数据结构:
type MapOverloading<Keys extends string, Values extends Record<Keys, unknown>> =
[Prop in Keys]: Map<Prop, Values[Prop]>
// type TypedMap =
// aa: Map<KeyType.aa, boolean>;
// bb: Map<KeyType.bb, string>;
//
type TypedMap = MapOverloading<KeyType, ValueTypes>
现在为了产生 Map 重载,我们需要获得 TypedMap
对象值的联合。
很简单,只要使用这个工具:
type Values<T> = T[keyof T]
现在我们需要将联合转换为交集 (UnionToIntersection
):
// credits goes to https://***.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type MapOverloading<Keys extends string, Values extends Record<Keys, unknown>> =
[Prop in Keys]: Map<Prop, Values[Prop]>
type Values<T> = T[keyof T]
type TypedMap = UnionToIntersection<Values<MapOverloading<KeyType, ValueTypes>>>
更多关于创建动态重载的信息你可以找到here
TypedMap
是一个重载的 Map
数据结构。让我们测试一下:
const tm: TypedMap = new Map();
tm.set(KeyType.aa, true) // ok
tm.set(KeyType.bb, true) // expected error
考虑这个例子:
export enum KeyType
aa = 'aa',
bb = 'bb'
export interface ValueTypes
aa: boolean,
bb: string
// credits goes to https://***.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type MapOverloading<Keys extends string, Values extends Record<Keys, unknown>> =
[Prop in Keys]: Map<Prop, Values[Prop]>
type Values<T> = T[keyof T]
type TypedMap = UnionToIntersection<Values<MapOverloading<KeyType, ValueTypes>>>
const tm: TypedMap = new Map();
tm.set(KeyType.aa, true) // ok
tm.set(KeyType.bb, true) // expected error
type IsNever<T> = [T] extends [never] ? true : false
type TupleToMap<Tuple extends any[], ResultMap extends Map<any, any> = never> =
Tuple extends []
? ResultMap
: Tuple extends [[infer Key, infer Value], ...infer Rest]
? IsNever<ResultMap> extends true ? TupleToMap<Rest, Map<Key, Value>> : TupleToMap<Rest, ResultMap & Map<Key, Value>>
: never
type Validation<Tuple extends any[], CustomMap> = CustomMap extends TupleToMap<Tuple> ? Tuple : []
function setMany<
Key extends string,
Value,
CustmoMap extends Map<Key, Value>,
Tuple extends [Key, Value],
Change extends Tuple[],
>(
map: CustmoMap,
change: Validation<[...Change], CustmoMap>,
): void
for (const [key, value] of change)
map.set(key, value)
setMany(
tm,
[
[KeyType.aa, true], // ok
[KeyType.aa, false] // ok
]
)
setMany(
tm,
[
[KeyType.bb, 's'], // ok
[KeyType.bb, 'hello'], // ok
]
)
setMany(
tm,
[
[KeyType.bb, 's'], // ok
[KeyType.aa, true, // ok
]
)
setMany(
tm,
[
[KeyType.aa, undefined] // expected error
]
)
setMany(
tm,
[
[KeyType.aa, new Set()] // expected error
]
)
setMany(
tm,
[
[KeyType.bb, undefined] // expected error
]
)
setMany(
tm,
[
[KeyType.bb, new Set()] // expected error
]
)
setMany(
tm,
[
[KeyType.bb, false] // expected error
]
)
setMany(
tm,
[
[KeyType.bb, true] // expected error
]
)
setMany(
tm,
[
[KeyType.aa, 'any string'] // expected error
]
)
Playground 以上示例回答了您的第一个问题:如何使其与元组一起使用。
但是有一个缺点,如果元组元素中的一个无效,整个元组会被高亮显示
请让我知道它是否适合您。如果是,我会提供更多解释。
【讨论】:
哇,有趣。但是 setMany 函数将 TKey 检测为 KeyType.bb 并将 TVal 检测为字符串,并且不接受 aa 和 boolean 元组。似乎它只适用于交叉路口的一种地图类型。 @ФилиппФастер 请提供测试用例。你期望什么工作,你期望什么失败。对我来说会更容易setMany(tm, [[KeyType.aa, true], [KeyType.aa, false], [KeyType.bb, 'anystring']])
。所以它应该接受具有 KeyType 及其对应的 ValueTypes 类型的元组。我尝试自己修复它,但我坚持同样的误解 - 我不知道如何正确输入元组:)
@ФилиппФастер 更新了元组。您是否希望第二个参数也是地图?如果是,它应该是什么类型的地图?和第一个参数一样吗?
哇,抱歉耽搁了这么久。这个解决方案很棒!非常感谢!以上是关于如何键入具有相应类型的元组数组?的主要内容,如果未能解决你的问题,请参考以下文章