我怎样才能让类型缩小在这里工作?`

How can I get type narrowing to work here?`

我没有从缩小联合类型上得到预期的结果。这是捕获问题的片段:

interface A {
    a : number;
}

interface B {
    b : string;
}

const isAnA = (arg : A | B) : arg is A => {
    return "a" in arg;
}

const applyFunc = <T>(func : (x : T) => number, arg : T) => {
    return func(arg)
}

const doTheThing = (arg : A | B) => {
    let f;
    if (isAnA(arg)) {
        f = (x : A) => x.a * 2;
    } else {
        f = (x : B) => parseInt(x.b) * 2;
    }
    return applyFunc(f, arg);
}

我期望这里发生的是 isAnA() 类型保护会让编译器知道 f 有类型 (x : A) => number 如果 arg 是一个 A,如果 argB,则键入 (x : B) => number,允许 applyFuncargf 上一起调用。

但是,我从编译器那里得到这个:

  Type '(x: B) => number' is not assignable to type '(x: A) => number'.
    Types of parameters 'x' and 'x' are incompatible.
      Property 'b' is missing in type 'A' but required in type 'B'.

除了显式地对 applyFunc 的调用进行类型保护之外,还有什么方法可以使它正常工作吗?

要做的一件事是改用条件运算符 - TypeScript 在重新分配最小化时效果最佳。但是仍然存在类型 (x: A => number) | (x: B) => number) 与参数类型 (A | B).

不合需要地分开的问题

我认为这里最好的方法是在缩小的条件内调用所需的函数,以避免以后必须重新缩小,例如:

const doTheThing = (arg: A | B) => {
    return isAnA(arg)
        ? applyFunc((x: A) => x.a * 2, arg)
        : applyFunc((x: B) => parseInt(x.b) * 2, arg);
}

一般来说,如果你想告诉 Typescript 两个变量具有相关类型,即 "要么 argA 并且 func 接受 A,或者 argB 并且 func 接受 B",那么它们需要是某个具有可区分联合类型的对象的属性:

{arg: A, func: (x: A) => number} | {arg: B, func: (x: B) => number}

要使其在您的代码中实际工作,需要使用分布式条件类型构建可区分联合,这样编译器就不会抱怨 applyFuncWithObject 函数。我通过使 ArgAndFunc 成为参数化类型来解决这个问题,所以上面的可区分联合是 ArgAndFunc<A | B>.

type ArgAndFunc<T> = T extends unknown ? {arg: T, func: (x: T) => number} : never

function applyFuncWithObject<T>(obj: ArgAndFunc<T>) {
    // or directly: return obj.func(obj.arg)
    return applyFunc(obj.func, obj.arg);
}

function doTheThing(arg: A | B) {
    let obj: ArgAndFunc<A | B>;
    if(isAnA(arg)) {
        obj = {arg, func: (x: A) => x.a * 2};
    } else {
        obj = {arg, func: (x: B) => parseInt(x.b) * 2};
    }
    return applyFuncWithObject<A | B>(obj);
}

Playground Link

我不知道这是否能完全满足您的需求,因为它不允许您在任何地方将 argfunc 作为两个单独的参数传递(无需编写通用辅助函数像 applyFuncWithObject)。另一方面,如果您确实需要在多个地方同时传递 argfunc,那么传递单个对象可能会简化您的代码。