假设我有这样的类型:
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限制,但是是否可以用这种样式编写代码以便进行类型检查?
答案 0 :(得分:1)
是的,这里没有方便且类型安全的解决方案。我已经为此打开了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
}
}
那行得通,但是令人讨厌。所以我建议一个断言。
希望有所帮助;祝你好运!