TypeScript 函数重载怎么写?适合什么场景?
简化版
TypeScript 函数重载是为同一个函数声明多组调用签名,再用一个实现签名承接具体逻辑。它适合“入参形态不同,返回类型也跟着变化”的 API,但实现签名必须兼容所有重载签名。
详细版
示例:
function format(value: string): string;
function format(value: number): string;
function format(value: string | number) {
return String(value);
}
前两行是重载签名,最后一行是实现签名。 调用方只能看到重载签名,看不到实现签名。 面试重点是:重载顺序、实现兼容、和联合类型的取舍。
完整版教学
一、重载解决返回类型随入参变化的问题
联合类型可以表达多个入参类型。 但如果返回类型和具体入参强相关,单纯联合类型可能不够精确。 函数重载可以给不同调用方式配置不同返回类型。
function get(id: number): User;
function get(ids: number[]): User[];
function get(input: number | number[]) {
return Array.isArray(input) ? input.map(id => ({ id })) : { id: input };
}
传 1 返回 User。
传 [1, 2] 返回 User[]。
这比直接写 (number | number[]) => User | User[] 更方便调用方使用。
二、重载签名和实现签名要分清
重载函数由多条重载签名和一条实现签名组成。 重载签名给调用方看。 实现签名负责真实函数体,并且必须能覆盖所有重载情况。
function parse(value: string): object;
function parse(value: Buffer): object;
function parse(value: string | Buffer): object {
return JSON.parse(value.toString());
}
流程如下:
调用方传参
-> TS 从上到下匹配重载签名
-> 选中某个调用签名
-> 运行时执行同一个函数体
实现签名不是额外的公开重载。 如果调用方式只符合实现签名但不符合任何重载签名,调用方仍会报错。
三、重载顺序会影响匹配
TypeScript 会按顺序匹配重载签名。 更具体的签名应该放在更前面,更宽泛的签名放后面。 否则宽泛签名可能提前吃掉调用,导致返回类型不够准确。
function fn(value: 'a'): 1;
function fn(value: string): number;
function fn(value: string) {
return value === 'a' ? 1 : 0;
}
const x = fn('a'); // 1
如果把 string 签名放在前面,'a' 可能先匹配宽泛签名。
面试里提到重载顺序,说明你知道它不是简单声明堆叠。
四、联合类型和重载怎么选
如果返回类型不随入参变化,联合类型通常更简单。 如果不同入参对应明显不同返回类型,重载更清晰。 不要为了展示技巧而滥用重载。
| 场景 | 推荐 |
|---|---|
| 入参多种类型,返回固定 | 联合类型 |
| 入参和返回一一对应 | 函数重载 |
| API 分支很多且复杂 | 考虑拆函数或泛型 |
| 回调类型高度相关 | 重载或泛型 |
例如 format(string | number): string 用联合类型即可。
query(id): Item、query(ids): Item[] 更适合重载。
五、重载不改变运行时实现
TypeScript 重载只是类型层面的多个调用签名。 编译后 JavaScript 里只有一个函数实现。 所以运行时仍要自己判断参数类型。
function double(value: string): string;
function double(value: number): number;
function double(value: string | number) {
if (typeof value === 'string') return value + value;
return value * 2;
}
记忆钩子:重载给类型系统多张入口说明书,运行时还是同一个门。
如果实现里没有正确分支处理,类型再漂亮也挡不住运行时错误。
六、箭头函数重载要换写法
函数声明天然支持多条重载签名。 箭头函数不能直接写多条同名声明。 可以用接口或类型别名声明多个调用签名,再赋值实现。
type Double = {
(value: string): string;
(value: number): number;
};
const double: Double = (value: string | number) => {
return typeof value === 'string' ? value + value : value * 2;
};
这在 React hooks、工具函数导出中经常遇到。 如果团队偏爱箭头函数,要知道这种替代写法。
七、常见误区与追问
- 误区:实现签名也能被调用方直接使用。 调用方只按重载签名检查,实现签名不作为公开调用入口。
- 误区:重载会生成多个 JavaScript 函数。 编译后只有一个实现函数,重载只存在于类型层。
- 误区:所有联合类型都应该改成重载。 返回类型不随入参变化时,联合类型通常更简单。
- 追问:重载签名顺序怎么放? 更具体的放前面,更宽泛的放后面。
- 追问:实现签名要满足什么条件? 它必须能兼容所有重载签名的参数和返回处理。
- 追问:箭头函数怎么实现重载? 用带多个调用签名的 type 或 interface 描述函数类型,再赋值实现。
八、加强记忆
函数重载按“三段”记:多条公开重载签名,一条宽泛实现签名,一个运行时分支实现。选择时看返回类型是否随入参改变;如果只是参数可以多种类型,联合类型往往更清楚。