预期有 3 个类型参数但得到 1 个但它应该推断出 2 种类型

Expected 3 type arguments but got 1 but it should infer 2 types

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

假设一个简单的界面

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

从 TS3.4 开始,没有 partial type parameter inference. Either you let the compiler try to infer all the type parameters, or you specify all the type parameters. (Well, there are default type parameters but that doesn't give you what you want: you want to infer the type parameters you leave out, not assign a default type to them). There have been several proposals to address this, but so far none have met with full approval

因此,目前只有解决方法。我能想到的两个是使用虚拟函数参数或使用 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");

这里我们正在做你说你不想做的事情...传入一个类型为 T 的参数。但由于它只是一个虚拟参数并且不在运行时使用,所以它只与类型系统认为它是什么有关。您传入的值的实际类型并不重要。所以你可以只传递它 null 并使用 type assertion 选择 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")

此处您返回一个函数,returns 另一个函数。第一个函数不接受任何值参数,但它接受您要指定的一个类型参数。然后它 returns 指定了 T 但推断其他类型参数的函数。

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