如何键入具有相应类型的元组数组?
How to type tuple array with corresponding types?
谁能帮我解决这个不寻常的(我认为的)问题?
最初,我实现的是类型化 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
)
我假设您想通过 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
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
C考虑这个例子:
export enum KeyType {
aa = 'aa',
bb = 'bb'
}
export interface ValueTypes {
aa: boolean,
bb: string
}
// credits goes to
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
以上示例回答了您的第一个问题:How to make it work with tuples.
但是有一个缺点,如果一个元组元素无效,整个元组将被高亮显示
请告诉我它是否适合您。如果是的话,我会提供更多的解释。
谁能帮我解决这个不寻常的(我认为的)问题?
最初,我实现的是类型化 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
)
我假设您想通过 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
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
C考虑这个例子:
export enum KeyType {
aa = 'aa',
bb = 'bb'
}
export interface ValueTypes {
aa: boolean,
bb: string
}
// credits goes to
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 以上示例回答了您的第一个问题:How to make it work with tuples.
但是有一个缺点,如果一个元组元素无效,整个元组将被高亮显示
请告诉我它是否适合您。如果是的话,我会提供更多的解释。