打字稿:类型“字符串”在函数的返回值中不可分配给类型“从不”

api*_*art 5 typescript

基本上我感兴趣的是为什么这个例子有效:

interface Alpha {}
interface Beta {}

interface HelloMapping {
  'Alpha': Alpha
  'Beta': Beta  
}

const hello = <T extends keyof HelloMapping>(arg: T): HelloMapping[T] => 
{
  if (arg === 'Alpha') return {} as Alpha;
  return {} as Beta;
};

const a = hello('Alpha') // a is of interface Alpha
Run Code Online (Sandbox Code Playgroud)

但这个没有:

interface HelloMapping2 {
  'Alpha': string
  'Beta': number
}

const hello2 = <T extends keyof HelloMapping2>(arg: T): HelloMapping2[T] => 
{
  if (arg === 'Alpha') return "42" as string
  return 42 as number
};
Run Code Online (Sandbox Code Playgroud)

Type 'string' is not assignable to type 'never'在函数代码的第一行出错。唯一的区别是将接口更改为stringnumber类型

请注意,我知道如何使用函数重载使其工作,但我很好奇为什么它不起作用。

jca*_*alz 5

第一个适用于@zerkms 指出的原因:Alpha并且Beta是相同的类型。TypeScript 的类型系统是结构性的,而不是名义上的。如果AlphaBeta具有相同的形状(即空类型{}),则它们是相同的类型,尽管名称不同。所以让我们忽略这个例子,因为它依赖于一个偶然的类型标识。


第二个示例不起作用的原因是控制流分析不会缩小泛型范围(请参阅microsoft/TypeScript#24085)。该检查arg === 'Alpha'无法说服T现在的编译器'Alpha'。这样做通常是不合理的。如果 type 有多个可能的值T,则检查 type 的一个值T不一定对其他任何值都有影响。

目前没有针对此问题的干净解决方案,尽管已提出可能解决此问题的建议。一种建议是允许您指定泛型类型必须恰好是 union 的一个字面成员,以便存在的任何类型值T都必须相同,并且在缩小一个值时可以安全地缩小所有值。

如果你想看到这个实现或解决的情况,你可能想要转到那些 GitHub 问题,并给他们一个或描述你的用例,如果它特别引人注目的话。

那么,这期间该怎么办呢?


总是允许实现编译没有错误的答案是使用类型断言重载(它们是我所谓的“道德上”等价物,因为重载本质上断言了参数的类型并返回为实现签名中的那些)。在您的情况下,它们看起来像这样(我知道您知道如何做到这一点,但后来来的其他人可能不会):

const helloAssert = <T extends keyof HelloMapping>(arg: T): HelloMapping[T] => {
    if (arg === 'Alpha') {
        return "42" as HelloMapping[T];
    }
    return 42 as HelloMapping[T];
};

function helloOverload<T extends keyof HelloMapping>(arg: T): HelloMapping[T];
function helloOverload(arg: keyof HelloMapping) {
    if (arg === 'Alpha') {
        return "42"
    }
    return 42;
}
Run Code Online (Sandbox Code Playgroud)

但是,这些有点不安全,因为尽管它们会捕获完全疯狂的错误(例如,return true),但它们不会捕获简单的混淆(例如,在测试中更改===!==)。


然而,有时候,你可以通过放弃控制流的分析来避免这些不安全的解决方案,并直接代表通用型操作:对象o类型O和密钥k类型的K收益率可读属性o[k]类型O[K],即使OK是通用的:

const helloMap = <T extends keyof HelloMapping>(arg: T): HelloMapping[T] =>
    ({ Alpha: "42", Beta: 42 })[arg];
Run Code Online (Sandbox Code Playgroud)

所以你给了一个实际的HelloMapping实例helloMap()并索引到它。当然,这可能不适用于所有用例,但有时以与编译器一起工作而不是与编译器相反的方式做事是有用的。


代码链接