TypeScript 函数重载 -> 箭头函数方式 报错处理;

522 阅读9分钟

函数重载

最近在读阮一峰老师的 TS 文档教程,遇到了 函数重载;然后自己摸索了一下 函数重载 相关知识,但是我想着箭头函数也是函数,能不能做函数重载呢?然后我就试了试,并遇到了一些报错;下面是我 对 箭头函数 做 函数重载报错的处理方式;供大家参考;

因自己理解的也不是很深,有什么错误的地方还请大佬指正;万分感谢 🙏


什么是函数重载

有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为。这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)。

reverse('abc');//cba
reverse([1,2,3]);// [3,2,1]

上面的reverse()可以将参数颠倒输出。参数可以是字符串,也可以是数组;

这意味着,该函数内部有处理字符串和数组的两套逻辑,根据参数类型的不同,分别执行对应的逻辑。这就叫“函数重载”。

TypeScript 对于“函数重载”的类型声明方法是,逐一定义每一种情况的类型。

function reverse(str: string): string; 
function reverse(arr: any[]): any[];

上面示例中,分别对函数reverse()的两种参数情况,给予了类型声明。但是,到这里还没有结束,后面还必须对函数reverse()给予完整的类型声明。

function reverse(str: string): string;
function reverse(arr: any[]): any[];
function reverse(stringOrArray: string | any[]): string | any[] {
  if (typeof stringOrArray === "string")
    return stringOrArray.split("").reverse().join("");
  else return stringOrArray.slice().reverse();
}

有一些编程语言允许不同的函数参数,对应不同的函数实现。但是,JavaScript 函数只能有一个实现,必须在这个实现当中,处理不同的参数。因此,函数体内部就需要判断参数的类型及个数,并根据判断结果执行不同的操作。

function add(x: number, y: number): number;
function add(x: any[], y: any[]): any[];
function add(x: number | any[], y: number | any[]): number | any[] {
  if (typeof x === "number" && typeof y === "number") {
    return x + y;
  } else if (Array.isArray(x) && Array.isArray(y)) {
    return [...x, ...y];
  }

  throw new Error("wrong parameters");
}

上面示例中,函数add()内部使用if代码块,分别处理参数的两种情况。

注意,重载的个别类型描述与函数的具体实现之间,不能有其他代码,否则报错。

以上就是阮老师函数重载的描述以及示例;


那么看完这个示例我就想着,能不能把 函数声明式改为箭头函数的方式;于是我就改了改,代码如下:

  type Adds = {
        (x: number, y: number): number;
        (x: any[], y: any[]): any[];
    };
    
   const add_: Adds = (x: number | any[], y: number | any[]): number | any[] => {
        if (typeof x === 'number' && typeof y === 'number') {
            return x + y;
        } else if (Array.isArray(x) && Array.isArray(y)) {
            return [...x, ...y];
        }

        throw new Error('warning parameters for add');
    }; 

结果就报错了:

报错信息:

不能将类型(x: number | any[], y: number | any[]) => number | any[]分配给类型Adds
不能将类型number | any[]分配给类型number
不能将类型any[]分配给类型number

于是我就去网上搜 相关问题 及报错信息;零零散散 不太全面;有的是说把返回值设为 any ;哎!确实不报错了哈;

 type Adds = {
        (x: number, y: number): number;
        (x: any[], y: any[]): any[];
    };
    
   const Add_: Adds = (x: number | any[], y: number | any[]): any => {
        if (typeof x === 'number' && typeof y === 'number') {
            return x + y;
        } else if (Array.isArray(x) && Array.isArray(y)) {
            return [...x, ...y];
        }

        throw new Error('warning parameters for add');
    }; 

但是这样的话就失去了 ts 的意义;于是我又开始对此问题进行钻研,终让我找到了答案;


报错原因

这个错误是由于 TypeScript 在函数类型的兼容性检查中发现了不匹配的问题。在代码中,定义了一个类型 Adds,它是一个函数类型,接受两个参数并返回一个值。但是,当你尝试将实际的 函数Add_ 分配给 Adds 类型时,TypeScript 检测到了类型不匹配的情况。

Adds 的第一个函数的签名为:接受两个参数number,返回一个number;Adds 的第二个函数的签名为:接受两个参数any[],返回一个any[]; 而我的 Add_ 函数声明返回类型是 number | any[],而 Adds 类型 的第一个签名和第二个签名的类型都不一致。所以导致了类型不一致的错误。

细说

也就是说 这个 add_函数 的返回类型是 (x: number | any[], y: number | any[]): number | any[],这意味着这个函数可以返回 number 或者 any[]

然而,这与 Adds 类型的第一个函数签名 (x: number, y: number): number 不匹配,因为在这个签名下,函数应该返回 number,而不是 number | any[]

Adds类型的第二个函数签名(x: any[], y: any[]): any[]也不匹配,在这个签名下应当返回any[],而不是number|any[];

这就是为什么 TypeScript 报错的原因。报错信息中指出,不能将返回类型为 number | any[] 的函数分配给 Adds 类型,因为它违反了 Adds 类型的第一个函数签名的规定,即返回值应该是 number。或者是违反了adds类型的第二个函数签名的规定,即返回值应该是any[];


那么-我们把代码改成以下这样就不报错了

此时,Adds 的两个函数的定义的返回值类型都与 add_函数的返回值一致;所以报错消除;(当然这是不符合预期的)

type Adds = {
    (x: number, y: number): number | any[];
    (x: any[], y: any[]): number | any[] ;
};


const add_: Adds = (x: number | any[], y: number | any[]): number | any[] => {
    if (typeof x === 'number' && typeof y === 'number') {
        return x + y;
    } else if (Array.isArray(x) && Array.isArray(y)) {
        return [...x, ...y];
    } else {
        throw new Error('warning parameters for add')
    }
};
// 代码执行正常;
console.log(add_([1,2,3], ['a', 'b', 'c'])); // [ 1, 2, 3, 'a', 'b', 'c' ]

问题解决

了解了报错原因之后,想要解决以上的错误信息也很简单;

1.你需要确保 Add_ 函数的返回类型与 Adds 类型的每个签名的返回值相匹配。

2.通过给 add_函数进行断言 as Adds,你明确地告诉 TypeScript 将这个函数表达式视为 Adds 类型。这种方式足够清晰地表明函数符合 Adds 类型的定义,从而消除了兼容性检查的问题;


最终代码如下:

    type Adds = {
        (x: number, y: number): number;
        (x: any[], y: any[]): any[];
    }

    const add_: Adds = ((x: number | any[], y: number | any[]): number | any[] => {
        if (typeof x === 'number' && typeof y === 'number') {
            return x + y;
        } else if (Array.isArray(x) && Array.isArray(y)) {
            return [...x, ...y];
        }

        throw new Error('warning parameters for add');
    }) as Adds;

其他相关问题:

1.使用 typeof 判断 x和y 都为 number 的情况下,按道理来说 在这个条件下 return x + y 的类型一定是 number,x 和 y 的类型都是 number 才会进入这个判断里面。那为什么 return x + y 不一定是 number

当检查 typeof x === 'number' && typeof y === 'number' 时,TypeScript 确定 xy 都是 number 类型。因此,在这个条件分支内,TypeScript 应该知道 x + y 的结果是 number

然而,TypeScript 的类型系统并不总是能够完全理解所有的复杂逻辑。有时候,即使你在代码中做了显而易见的类型检查,TypeScript 仍然可能会感到困惑。

TS 既然不能理解所有的复杂逻辑,那么我使用断言来告诉 Ts 返回值的类型;(依旧报错)

type Adds = {
    (x: number, y: number): number;
    (x: any[], y: any[]): any[];
};

const add_: Adds = (x: number | any[], y: number | any[]): number | any[] => {
    if (typeof x === 'number' && typeof y === 'number') {
        return (x + y) as number;
    } else if (Array.isArray(x) && Array.isArray(y)) {
        return [...x, ...y] as any[];
    } else {
        throw new Error('Invalid')
    }
};

2.为什么在使用类型断言或者明确指定函数返回类型时,报错依旧存在?

在 TypeScript 中,类型断言或者明确指定函数返回类型可以帮助解决函数的类型推断问题。然而,即使你在函数体内使用了类型断言或者明确指定了返回类型,TypeScript 在进行函数类型的兼容性检查时仍然会依赖于函数签名。也就是说,TypeScript 会检查函数的签名是否与目标类型 Adds 的定义相匹配。

3.为什么整体断言为 Adds 类型会解决问题,(或者函数返回值类型adds类型一致)?

类型一致就不说了,上文已经解释过了;

断言:

当你对整个函数表达式使用类型断言时,相当于告诉 TypeScript:“我知道这个函数符合 Adds 类型的定义,所以不要再进行额外的兼容性检查了。”这样,TypeScript 就会将整个函数表达式视为 Adds 类型,不再进行细致的检查。


不建议使用箭头函数做函数重载:

如果你真的需要使用箭头函数,可以考虑合并函数签名的方式,而不是使用函数重载。下面是一个使用箭头函数的例子

type AType = number | any[];
type Adds = (x: AType, y: AType) => number | (AType)[];

const Add_: Adds = (x: AType, y: AType): number | (AType)[] => {
    if (typeof x === 'number' && typeof y === 'number') {
        return x + y;
    } else if (Array.isArray(x) && Array.isArray(y)) {
        return [...x, ...y];
    }

    throw new Error('warning parameters for add');
};

console.log(Add_([1, 2, 3], ['a', 'b', 'c']));

箭头函数不太适合函数重载,因为箭头函数无法像普通函数那样进行函数重载.

箭头函数在JavaScript中确实是一个非常方便的特性,它提供了一种简洁的语法来编写函数。然而,它们并不适合所有的场景,特别是在函数重载的情况下。

首先,箭头函数没有自己的this。它们从封闭的作用域中继承this值。这可能会导致在需要使用特定this上下文的方法中出现问题,尤其是在构造函数中使用箭头函数时,会抛出异常,因为箭头函数不能作为构造函数。

其次,箭头函数不支持对象的arguments对象。在传统的函数中,可以使用arguments对象来访问传递给函数的所有参数,但是在箭头函数中,这个对象是不可用的。

最后,箭头函数的重载解析与普通函数不同。由于箭头函数的语法限制,它们在重载时可能不会按照预期工作。例如,箭头函数体内的表达式必须用括号包裹,否则可能会导致代码逻辑错误。

综上所述,虽然箭头函数在很多情况下都非常有用,但在需要函数重载的场景中,它们的一些限制使得它们不是最佳选择。在这些情况下,使用传统的函数声明可能是更好的选择。


🐾以上答案均来自 chatGpt 仅供参考


附:

阮一峰 TypeScript 教程 这是一篇非常好的文档;

👋 bye ~

hello-01.jpg