首页 > 解决方案 > 预期 3 个类型参数,但得到 1 个,但它应该推断出 2 个类型

问题描述

我想知道如何正确推断我的函数的第二个和第三个模板

假设一个简单的界面

interface ISome {
    a: string;
    b?: {
        c: string;
    };
}

关注作品

function pathBuilder<
    K1 extends keyof ISome,
    K2 extends keyof NonNullable<ISome[K1]>>(p: K1, p2?: K2) {
    let res = String(p);
    if (p2) { res += "." + p2; }
    return res;
}

const pathTest = pathBuilder("b", "c"); // ---> "b.c" and intellisense works on parameters

但我需要通过指定另一种类型来概括该函数的工作(我不想传递对象实例来指定类型)

所以,以下不起作用

function pathBuilder<
    T,
    K1 extends keyof T,
    K2 extends keyof NonNullable<T[K1]>>(p: K1, p2?: K2) {
    let res = String(p);
    if (p2) { res += "." + p2; }
    return res;
}

const pathTest = pathBuilder<ISome>("b", "c"); // ERROR: Expected 3 type arguments, but got 1.ts(2558)

似乎函数的第二个和第三个模板参数不是从第一个参数推断出来的,但它应该是因为在第一种情况下,当我直接指定类型 T=ISome 时它起作用了。

我不确定是否有一些语言关键字可以使其工作,但模板应该完全适用:指定未知类型。

编辑

实际上我找到了这种方式,但需要额外的编码,如果可能的话我会避免

function pathBuilder<T>() {
    return <
        K1 extends keyof T,
        K2 extends keyof NonNullable<T[K1]>>(p: K1, p2?: K2) => {
        let res = String(p);
        if (p2) { res += "." + p2; }
        return res;
    };
}

const pathTest = pathBuilder<ISome>()("b", "c");

标签: typescripttype-inferenceextendskeyof

解决方案


从 TS3.4 开始,没有部分类型参数推断。要么让编译器尝试推断所有类型参数,要么指定所有类型参数。(嗯,有默认类型参数,但这并没有给你你想要的:你想推断你遗漏的类型参数,而不是为它们分配默认类型)。已经有几个提议来解决这个问题,但迄今为止没有一个得到完全批准

因此,目前只有解决方法。我能想到的两个是使用虚拟函数参数或使用currying

虚拟参数版本:

function pathBuilderDummy<
    T,
    K1 extends keyof T,
    K2 extends keyof NonNullable<T[K1]>>(dummy: T, p: K1, p2?: K2) {
    let res = String(p);
    if (p2) { res += "." + p2; }
    return res;
}

const pathDummyTest = pathBuilderDummy(null! as ISome, "b", "c");

在这里,我们正在做你说你不想做的事情......传入一个 type 的参数T。但是由于它只是一个虚拟参数并且在运行时不使用,所以它只关心类型系统认为它是什么。您传入的值的实际类型无关紧要。所以你可以通过它null并使用类型断言来选择T

柯里化函数解决方案:

const pathBuilderCurry =
    <T>() => <
        K1 extends keyof T,
        K2 extends keyof NonNullable<T[K1]>>(p: K1, p2?: K2) => {
        let res = String(p);
        if (p2) { res += "." + p2; }
        return res;
    }

const pathCurryTest = pathBuilderCurry<ISome>()("b", "c")

在这里,您将返回一个返回另一个函数的函数。第一个函数不接受值参数,但它确实接受您要指定的一个类型参数。然后它返回一个指定的函数,T但推断其他类型参数。

这两种解决方案都不是完美的,但它们是我们目前能做的最好的。希望有帮助;祝你好运!


推荐阅读