如何正确键入“调度程序”样式函数的“处理程序”查找

时间:2019-03-25 19:07:53

标签: typescript

假设我有这样的类型:

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.valuenumber。因此,handlers[info.valueType]可以与info.value一起调用。
  • 如果info.valueType'str',那么我们可以确定handlers[info.valueType](value: string) => any)info.valuestring。因此,handlers[info.valueType]可以与info.value一起调用。

我不确定这是否是Typescript限制,但是是否可以用这种样式编写代码以便进行类型检查?

1 个答案:

答案 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
    }
}

那行得通,但是令人讨厌。所以我建议一个断言。

希望有所帮助;祝你好运!