ts 函数重载的问题

yepnope
  • 243

下面的3个函数的重载。
test写法的重载会报错。
test1和test2就是正常的,这是为什么?

function test (): void;
function test(name: string): string; 
function test(name: number): number; 

function test (name?: string | number,) {
  
  if(typeof name === 'string') {
    return '1';
  } else if(typeof name === 'number') {
    return 1;
  }
    
  return;
}

function test2 (): void;
function test2(name: string): string; 
function test2(name: number): number; 

function test2 (name?: string | number,) {
  
  if(typeof name === 'string') {
    return name;
  } else if(typeof name === 'number') {
    return name;
  }
    
  return;
}

function test3 (): void;
function test3(name: string): string; 
function test3(name: number): number; 

function test3 (name?: string | number,): string | number | void {
  
  if(typeof name === 'string') {
    return '1';
  } else if(typeof name === 'number') {
    return 1;
  }
    
  return;
}

代码:
https://www.typescriptlang.or...

回复
阅读 697
4 个回答
✓ 已被采纳

因为类型不兼容,你可能觉得返回字符1和数字1是符合stringnumber的,但需要注意的是函数重载列表必须是函数实现的子集

function test (): void;
function test(name: string): string; 
function test(name: number): number; 

function test (name?: string | number,) {
  ...
}

也就是前三个的类型是第四个函数实现的类型子集。而实现里返回值是‘1’|1|undefined;反过来了,这个需要注意理解,所以下面这种是成立的

function test2 (): void;
function test2(name: string): '1'; 
function test2(name: number): 1; 

function test2 (name?: string | number,) {
  
  if(typeof name === 'string') {
    return name;
  } else if(typeof name === 'number') {
    return name;
  }
    
  return;
}

image.png

没有一个类型是匹配的

十天了,我感觉还没几个赞就离谱,采纳回答 @zangeci 四个赞更离谱,@陈安柱 一个不明所以的都拿了两赞,气炸了气炸了。为什么 这串 TS 代码 没有报错呢?你们的回答能解释这个问题吗?

提问者 @yepnope 私信起来也是不知所云,“没看到我的答案”?如果你说的是采纳前没看到,现在也可以改呀,设个不太全面的第一不是误人子弟?

抱怨结束,原答案在下面。


函数重载返回值必须是函数实现的子集,或者超集。

TS 在检查一个函数重载的返回值与其实现的返回值是否兼容时,是直接看整体的。

  • 如果函数重载无返回值,兼容。
  • 如果函数重载的返回值是函数实现的返回值的子集,兼容。
  • 如果函数重载的返回值是函数实现的返回值的超集,也兼容

对于 test,我只以第二个函数重载为例,该重载返回值是 string,其函数实现返回值是 "1" | 1 | undefinedstring"1" | 1 | undefined 的子集吗?不是。是超集吗?不是。所以报错。

对于 test2,函数实现返回值是 string | number | undefined,也只以第二个函数重载为例,stringstring | number | undefined 的子集吗?是,所以不报错。

对于 test3,在函数实现上标记了返回值,TS 的检查分为两部分,一部分是实现内的返回是否符合标记的返回,另一部分和上述一样,不过以其标记值作为该函数实现的返回值。

“子集或超集” 这句,文档里有写吗?没写。但源码里有写。TS 的源码毫不吝啬变量名长度,是很好看的。在 src/compiler/checker.ts 的 17253-17268 行,因为文件太大 GitHub 看不了,我就复制过来:

function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean {
    const erasedSource = getErasedSignature(implementation);
    const erasedTarget = getErasedSignature(overload);

    // First see if the return types are compatible in either direction.
    const sourceReturnType = getReturnTypeOfSignature(erasedSource);
    const targetReturnType = getReturnTypeOfSignature(erasedTarget);
    if (targetReturnType === voidType
        || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation)
        || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) {

        return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true);
    }

    return false;
}

除了源码里有写,还有一堆相关 issue(这不一定算个好事因为 Duplicate 链真的好长)。近期的 #44661 值得注意,TS 成员再次提到了这个子集或超集的判断方式,也开始考虑是否纳入交集判断。

常量 "1"1 会被推断为字面量类型。

对于 test,推断返回类型会是 "1" | 1 | undefined,不包含重载声明中的 stringnumber

解决办法:

  • as 扩大 "1"1 的类型,比如 return "1" as string;
  • 显式声明函数实现部分的返回类型为 string | number | undefined

来学习我的课程:TypeScript从入门到实践 【2021 版】 - 思否编程

image.png

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
你知道吗?

宣传栏