我有一个通过映射对象类型TMap
它旨在为处理程序函数提供一种命名类型与对应类型值的关联
interface Thing<TMap extends { [k: string]: any }> {
get<T extends keyof TMap>(handler: (v: TMap[T], t: T) => unknown): unknown
}
例如,将“映射对象”类型设置为:
interface Types {
num: number,
dat: Date,
str: string
}
和事物实例:
declare const thing: Thing<Types>
使用get
方法获得几个值,类型可以工作,但在检查类型时会失去类型关联:
thing.get((v, t) => {
// v: string | number | Date
// t: "num" | "dat" | "str"
if (t === 'num') {
// v: string | number | Date
v
} else if (t === 'dat') {
// v: string | number | Date
v
} else if (t === 'str') {
// v: string | number | Date
v
}
})
我设法解决了一个棘手的问题:
type Caster<TMap extends { [k: string]: any }> =
<T extends keyof TMap>(
v: TMap[keyof TMap],
t: keyof TMap,
isOfType: T
) => v is TMap[T]
declare const caster: Caster<Types>
thing.get((v, t) => {
// v: string | number | Date
// t: "num" | "dat" | "str"
if (caster(v, t, 'num')) {
// v: number
v
} else if (caster(v, t, 'dat')) {
// v: Date
v
} else if (caster(v, t, 'str')) {
// v: string
v
}
})
如何正确声明Thing
和Thing.get
以保持类型关联以避免棘手的黑客攻击?
[编辑] 在TSPlayground
上查看答案 0 :(得分:2)
类型防护不支持将值的类型缩小到与选中的值不同的类型。
我们所能做的就是更改回调以接收一个对象,该对象是一个联合体,其中联合体的成员具有{ type: P, value T[P] }
的形式,其中T
是地图类型,P
依次T
// Type that creates a union using the distributive property of union types.
type TypeUnion<T> = keyof T extends infer P ? // Introduce an extra type parameter P to distribute over
P extends any ? { type: P, value: T[P] } : // Take each P and create the union member
never : never;
interface Thing<TMap extends { [k: string]: any }> {
get(handler: (v: TypeUnion<TMap>) => unknown): unknown
}
declare const thing: Thing<Types>
interface Types {
num: number,
dat: Date,
str: string
}
thing.get(o => { // o is { type: "num"; value: number; } | { type: "dat"; value: Date; } | { type: "str"; value: string; }
if (o.type === 'num') {
o.value // number
} else if (o.type === 'dat') {
o.value // Date
} else if (o.type === 'str') {
o.value // string
}
})
TypeUnion
的条件类型的替代方法是使用映射类型,如下所示:
type TypeUnion<T> = {
[P in keyof T] : { type: P, value: T[P]}
}[keyof T]