当前位置: 首页 > 知识库问答 >
问题:

使用参数对函数进行类型推断

戚学文
2023-03-14

我最近一直在摆弄泛型类型,在编写一个函数时,我不得不使用函数中的一个参数来确定返回类型。

大致来说,我想做以下几点:

const getString = () => 'string';

const getNumber = () => 9999;

const getNumberOrString =
    <
        T extends 'number' | 'string',
        ReturnType extends T extends 'number' ? number : T extends 'string' ? string : void
    >(which: T): ReturnType => {
        switch (which) {
            case 'number':
                return getNumber();
            case 'string':
                return getString();
            default:
                return;
        }
    };

在Typescript平台上运行上述代码时,我遇到以下错误消息:

类型“number”不能分配给类型“ReturnType”“number”可分配给“ReturnType”类型的约束,但“ReturnType”可以用约束“string | number”的不同子类型实例化。(2322)

类型“string”不能分配给类型“ReturnType”“string”可分配给“ReturnType”类型的约束,但“ReturnType”可以用约束“string | number”的不同子类型实例化。(2322)

类型“undefined”不可分配给类型“ReturnType”ReturnType“可以用与“undefined”无关的任意类型实例化。(2322)

我可以让这段代码在没有任何错误的情况下运行的唯一方法是将返回语句类型转换为any,但是有没有其他方法可以做到这一点而不必求助于类型转换?

共有1个答案

夏学名
2023-03-14

这是TypeScript中的已知限制;依赖于未指定泛型类型参数的条件类型的计算被推迟,编译器不知道如何验证值是否可分配给它们。

getNumberOrString()的实现中,泛型类型参数T是未指定的(它仅在调用getNumberOrString()时被指定),因此返回类型T扩展了“数字”?数字:...是这些延迟类型之一。因此,当您尝试返回任何特定值时,例如返回getNum(),编译器无法验证number是否可分配给该类型,并且您会收到错误。

如果编译器可以使用控制分析之类的东西来理解返回getNumber()只能在T扩展'number'时发生,那就太好了,但目前情况并非如此。请参阅microsoft/TypeScript#33912以获取实现对此的支持的功能请求以及关于为什么这不是一个容易解决的问题的讨论。

因此,目前,编写具有通用条件返回类型的函数的唯一方法是使用类型断言(您称之为类型转换)或等价物(例如允许您松开实现签名的单调用签名重载)。

下面是编写函数的方法:

type NumOrStrReturnType<T> = 
  T extends 'number' ? number : 
  T extends 'string' ? string : 
  never;

const getNumberOrString =
    <T extends 'number' | 'string'>(which: T): NumOrStrReturnType<T> => {
        switch (which) {
            case 'number':
                return getNumber() as NumOrStrReturnType<T>;
            case 'string':
                return getString() as NumOrStrReturnType<T>;
            default:
                throw new Error("I DIDN'T EXPECT THAT");
        }
    };

请注意,GetNumberRorString如何只有一个泛型参数。看起来您使用了第二个泛型参数来为返回类型指定一个短名称,但它可能会导致奇怪的行为(例如,可以将其指定为比预期更窄的类型(例如,getNumberString)

还要注意,我没有返回,而是在不可能的默认情况下抛出了一个错误。顺便说一句,这是通用函数实现中控制流的另一个限制。即使在该子句中,which有可能被缩小到never,编译器甚至没有尝试,因为它不能缩小t本身。请参阅microsoft/TypeScript#13995和microsoft/TypeScript#24085以获取支持该功能的功能请求。你可以在没有抛出的情况下解决它,但我认为这太离谱了。

这里的要点是,使用泛型条件类型的任何版本的GetNumberRorString()都需要您,即实现者,负责维护类型安全,因为编译器无法完成TypeScript 4.1中的任务。

对于您编写的特定函数,我知道如何编写它以使编译器能够实际维护类型安全的唯一方法是将返回类型表示为属性查找:

interface NumOrStr {
    number: number;
    string: string;
}
const getNumberOrString2 =
    <K extends keyof NumOrStr>(which: K): NumOrStr[K] {
        return ({
            get number() { return getNumber() },
            get string() { return getString() }
        })[which];
    }

编译器将GetNumberRorString2()视为获取类型为“number”或“string”的参数,并返回类型为“number”或“string”的值,就像它在查找类型为NumOrStr的对象中的属性一样。实际上,实现就是这样(通过getter)。这可以根据需要工作:

console.log(getNumberOrString2("number").toFixed(2)); // 9999.00
console.log(getNumberOrString2("string").toUpperCase()); // STRING
const sOrN = getNumberOrString2(Math.random() < 0.5 ? "string" : "number");
// const sOrN: string | number

为了从编译器获得类型安全性,是否值得跳过这些障碍?可能不会。但至少可以在这里做,我想这很好。

游乐场链接到代码

 类似资料:
  • 问题内容: 有时需要检查Python中的参数。例如,我有一个函数可以接受网络中其他节点的地址作为原始字符串地址,也可以接受封装其他节点信息的类Node。 我使用type()函数,如下所示: 这是这样做的好方法吗? 更新1: Python 3具有函数参数的注释。可以使用以下工具将其用于类型检查:http ://mypy-lang.org/ 问题答案: 使用。样品:

  • 我有一个类,它有一些属性/方法,返回最初通过构造函数传递的值: 人们建议用泛型作为打字稿的等价物: 现在我有了一个子类,它调用超类的构造函数并传递以下选项: TypeScript编译器对此不满意,希望通过类型扩展Base类。但是我有两次信息,在超级呼叫和扩展符号中。什么是正确的ts语法来解决我的问题? (键入脚本中的代码)

  • 问题内容: 在下面的示例中,为什么编译器能够为in中的第一次调用推断出通用参数,而在第二次调用中却无法推断出通用参数?我正在使用Java 6。 (编译错误为 Nonsense.Bar类型的func(Nonsense.Foo)方法不适用于参数(Nonsense.Foo) )。 注意:我了解编译器错误可以通过test()中的第三行来解决-我很好奇是否存在阻止编译器推断类型的特定限制。这 似乎 对我有足

  • 问题内容: 我对Eclipse Galileo有一个奇怪的问题。 我将Java 1.6设置为我的JRE。在这行代码上 我在Eclipse的问题列表中看到以下错误: Collection类型不是通用的;不能使用参数对其进行参数化 我用Ant构建这个项目没有任何问题。 我该如何解决?看起来这是Eclipse问题,但是由于这个错误,我无法从IDE编译/发布项目。 问题答案: 对于那些从Google那里可

  • 使用以下代码生成时 生成以下诊断(代码后): 诊断: 有趣的部分不是关于歧义错误本身(这不是这里主要关注的问题)。有趣的是,当仅使用函数名调用fun时,第一个fun的模板参数F被解析为纯函数类型double(double),而第二个fun的模板参数F被解析为更期望的函数指针类型。 然而,当我们将调用<代码>乐趣(测试) 更改为<代码>乐趣( 这种行为似乎是所有Clang和GCC(以及Visual