TypeScript:可区分联合的通用 switchExpression 函数

TypeScript: General switchExpression function for discriminated unions

我正在尝试制作一个通用的 switch 表达式函数,它可以采用任何可区分的联合类型(为简单起见,使用类型 属性 作为鉴别器)及其鉴别器值到回调函数的映射和return 适当回调的结果。

例如

type One = {
  type: 'one',
  numeric: number
};

type Two = {
  type: 'two',
  text: string
};

type Union = One | Two;

const union: Union = ... // some appropriate assignment

// The function switchExp should be aware of what the map should 
// look like based on the type of its first arg. The argument
// passed to each callback should be properly discriminated based
// on the key in the map.
let result: number | string = switchExp(union, {
  one: u => u.numeric, // compiler should know that u is of type One
  two: u => u.text // compiler should know that u is of type Two
});

我们可以使用映射类型和条件类型ReturnValue来获得所需的效果。然而,我们可以推断函数参数类型的方式有一个障碍。如果我们尝试在单个函数调用中执行此操作,则参数将被键入为 any

例如,这不会按预期工作:

function switchExp2<T extends { type: string }, R extends { [P in (T["type"]]: (v: Extract<T, { type: P }>) => any }>(u: T, o: R): ReturnType<R[keyof R]> {
    return null as any;

}

let result2 = switchExp2(union, {
    one: u => u.numeric, // u is implictly tyed as any
    two: u => u.text // u is implictly tyed as any
});

编译器尝试从所有可能的站点推断 T 并且只是放弃而不是得出结论。简单的解决方案是先修复 T,然后再调用映射对象:

function switchExp<T extends { type: string }>(u: T) {
    return function <R extends { [P in T["type"]]: (v: Extract<T, { type: P }>) => any }>(o: R): ReturnType<R[keyof R]> {
        return null as any; // replace with reasonable implementation 
    }
}

let result: number | string = switchExp(union)({
    one: u => u.numeric, //u is of type One
    two: u => u.text //  u is of type Two
});