【问题标题】:How can I get type narrowing to work here?`我怎样才能让类型缩小在这里工作?
【发布时间】:2021-11-12 20:53:22
【问题描述】:

缩小联合类型的范围没有得到预期的结果。这是一个捕捉问题的 sn-p:

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) =&gt; number 如果argA,并且类型(x : B) =&gt; number 如果arg 是一个B,允许在argf 上同时调用applyFunc

但是,我从编译器中得到了这个:

  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 type-narrowing


    【解决方案1】:

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

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

    要使其在您的代码中真正起作用,需要使用分布式条件类型来构建可区分联合,因此编译器不会抱怨 applyFuncWithObject 函数。我通过将ArgAndFunc 设为参数化类型解决了这个问题,因此上面的可区分联合是ArgAndFunc&lt;A | B&gt;

    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,那么传递一个对象可能会简化您的代码。

    【讨论】:

    • 这不是我想要的完全,但它在不重复执行逻辑的情况下实现了目标。谢谢!
    【解决方案2】:

    要做的一件事是改用条件运算符 - 当重新分配最小化时,TypeScript 效果最好。但是仍然存在一个问题,即您的类型 (x: A =&gt; number) | (x: B) =&gt; 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);
    }
    

    【讨论】:

    • 当我说我想避免显式地对调用进行类型保护时,我指的是这种解决方案,因为它会导致对 applyFunc 的调用重复。在我遇到这个问题的代码中,我最终这样做了,但我宁愿避免重复自己。
    • @DavisYoshida 我相信这是一个很好的解决方案。函数类型的联合是一种难以处理的类型。大多数时候它是无用的,因为你想要一个交叉点,或者换句话说 - 超载。 AFAIK,没有优雅和类型安全的解决方案。
    • @captain-yossarian 我同意这是正确的。如果答案是您无法在不复制函数调用代码的情况下完成它,那么我将接受它作为答案。我会很感激一个指向打字稿文档中的东西的指针,指出限制是什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    • 2015-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-19
    相关资源
    最近更新 更多