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

问题描述 投票:3回答:1

假设我有这样的类型:

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限制,但是是否可以用这种样式编写代码以便进行类型检查?

typescript
1个回答
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
    }
}

这有效,但令人讨厌。所以我建议断言。

希望有所帮助;祝好运!

© www.soinside.com 2019 - 2024. All rights reserved.