预期有3个类型参数,但有1个参数,但应该推断出2个类型 [英] Expected 3 type arguments but got 1 but it should infer 2 types

查看:406
本文介绍了预期有3个类型参数,但有1个参数,但应该推断出2个类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

假设一个简单的界面

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)

似乎该函数的第2个和第3个模板参数不能从第一个参数推断出,但是应该这样做,因为在第一种情况下,当我直接指定类型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开始,没有默认类型参数,但这并不能满足您的需求:您想推断所遗漏的类型参数,而不是为其分配 default 类型).对于没有得到完全批准的会议.

因此,目前只有解决方法.我可以想到的两个是使用虚拟函数参数或使用 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并使用类型断言选择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,但推断出其他类型参数.

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

I wondering how to correctly infer 2th and 3th template of my function

suppose a simple interface

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

follow works

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

but I need to generalize the function to work by specify another type ( I don't want to pass an object instance to specify the type )

so, following not works

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)

seems that 2th and 3th template argument of the function doesn't infer from the first one but it should because in the case first case when I specified directly a type T=ISome it worked.

I'm not sure if there is some language keyword to make it work but the template should work exactly for that: specify an unknown type.

EDIT

Actually I found this way, but require extra coding I would avoid if possible

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

解决方案

As of TS3.4 there is no 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.

For now, therefore, there are only workarounds. The two that I can think of are to use a dummy function parameter or to use currying.

The dummy parameter version:

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

Here we are doing what you said you didn't want to do... pass in a parameter of type T. But since it's just a dummy parameter and not used at runtime, it only matters what the type system thinks it is. The actual type of the value you pass in doesn't matter. So you can just pass it null and use a type assertion to choose T.

The curried function solution:

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

Here you are returning a function that returns another function. The first function takes no value parameters but it does take the one type parameter you want to specify. It then returns a function where T is specified but the other type parameters are inferred.

Neither solution is perfect, but they are the best we can do for now. Hope that helps; good luck!

这篇关于预期有3个类型参数,但有1个参数,但应该推断出2个类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆