假设我有这样的类型:
type TInfoGeneric<TType extends string, TValue> = {
valueType: TType,
value: TValue, // Correspond to valueType
}
为了避免重复我的自我,我创建了一个类型映射,列出了可能的valueType
,并将valueType
与值的类型匹配。
type TInfoTypeMap = {
num: number;
str: string;
}
现在,为了实际创建TInfo
,我使用映射类型将所有类型映射到TInfoGeneric
,然后只获取它的值侧。
type TAllPossibleTInfoMap = {
[P in keyof TInfoTypeMap]: TInfoGeneric<P, TInfoTypeMap[P]>;
};
type TInfo = TAllPossibleTInfoMap[keyof TAllPossibleTInfoMap]; // TInfoGeneric<"num", number> | TInfoGeneric<"str", string>
然后,为了定义所有类型的处理程序,我只为处理程序创建另一个映射类型。
type TInfoHandler = {
[P in keyof TInfoTypeMap]: (value: TInfoTypeMap[P]) => any
};
const handlers: TInfoHandler = {
num: (value) => console.log(value.toString(16)),
str: (value) => console.log(value),
}
最后,为了实际使用处理程序,我创建了一个这样的函数:
function handleInfo(info: TInfo) {
handlers[info.valueType](info.value); // Error
}
我收到了这个错误:
Argument of type 'string | number' is not assignable to parameter of type 'number & string'.
Type 'string' is not assignable to type 'number & string'.
Type 'string' is not assignable to type 'number'.
通常情况下,handlers[info.valueType]
可能是((value: number) => any) | ((value: string) => any)
是可以理解的。但是,在这种情况下:
info.valueType
是'num'
,那么我们可以肯定handlers[info.valueType]
是(value: number) => any)
而info.value
是number
。因此,handlers[info.valueType]
可以用info.value
调用。info.valueType
是'str'
,那么我们可以肯定handlers[info.valueType]
是(value: string) => any)
而info.value
是string
。因此,handlers[info.valueType]
可以用info.value
调用。我不确定这是否是Typescript限制,但是是否可以用这种样式编写代码以便进行类型检查?
是的,这里没有方便和类型安全的解决方案。我已经开了一个关于这个的issue,但我完全期望答案是“太多的工作没有足够的好处来解决这个问题”。
我看到前进的两个主要方式。一个是使用type assertion,因为你合法地知道编译器在这里做的更多。它可能是这样的:
function handleInfo(info: TInfo) {
// assert your way out. Not type safe but convenient!
(handlers[info.valueType] as (x: number | string)=>any)(info.value);
}
现在没有错误。这不是类型安全的。但它很方便,不会改变发出的JavaScript。
或者你可以试着让编译器通过案例并证明一切都很好。这很复杂,很脆弱,并且具有运行时效果:
const typeGuards: {
[P in keyof TInfoTypeMap]: (x: TInfoTypeMap[keyof TInfoTypeMap])=>x is TInfoTypeMap[P];
} = {
num: (x:any): x is number => typeof x === "number",
str: (x:any): x is string => typeof x === "string"
}
function narrowTInfo<K extends keyof TAllPossibleTInfoMap>(
x: TInfo, v: K): x is TAllPossibleTInfoMap[K] {
return typeGuards[v](x.value);
}
function handleInfo(info: TInfo) {
if (narrowTInfo(info, "num")) {
handlers[info.valueType](info.value); // okay
} else {
handlers[info.valueType](info.value); // okay
}
}
这有效,但令人讨厌。所以我建议断言。
希望有所帮助;祝好运!