网站搜索

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


我正在尝试创建一个通用的 switch 表达式函数,它可以接受任何带辨别联合类型的值(为了简单起见,使用类型属性作为辨别器)以及一个将其辨别器值映射到回调函数的映射,并返回相应回调函数的结果。

例如

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
});

答案#1

我们可以使用映射类型和条件类型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
});