函数重载、never类型
一、函数重载(overload)
核心背景
日常使用 TS 时,很多人仅做简单类型标注,忽略了函数重载这个实用功能。它能解决多参数组合函数的类型提示不清晰、参数关系不明确的问题,提升代码可读性与开发体验,仅作用于编译阶段,不影响运行逻辑。
1、 常规类型声明的痛点
以一个 message 消息提示函数为例,它支持多种参数组合:
- 传入配置对象
{content: string, type: 'success' | 'error'} - 传入字符串 + 回调函数
- 传入字符串 + 消息类型 + 回调函数
常规写法的缺陷:
- 类型提示模糊,无法直观看到参数的合法组合
- 参数关系不明确,使用者难以判断不同参数的搭配规则
- 可读性差,类型定义冗长混乱
ts
// 常规写法:参数类型合并,提示不清晰
function message(
arg1: string | { content: string; type: 'success' | 'error' },
arg2?: ('success' | 'error') | (() => void),
arg3?: () => void
): void {
// 实现逻辑
}调用时,TS 无法精准提示参数组合,容易传参错误
2、 函数重载的实现
核心思路
- 先声明重载签名:罗列函数所有可能的参数组合(只声明,不实现)
- 再写实现签名:包含所有参数的兼容类型,编写具体逻辑
代码示例:message 函数重载
typescript
// ------------------- 1. 重载签名:明确所有合法参数组合 -------------------
// 组合1:传入配置对象
function message(options: { content: string; type: 'success' | 'error' }): void;
// 组合2:传入 字符串 + 回调函数
function message(content: string, callback: () => void): void;
// 组合3:传入 字符串 + 消息类型 + 回调函数
function message(content: string, type: 'success' | 'error', callback: () => void): void;
// ------------------- 2. 实现签名:兼容所有重载的参数类型 -------------------
function message(
arg1: string | { content: string; type: 'success' | 'error' },
arg2?: ('success' | 'error') | (() => void),
arg3?: () => void
): void {
// 具体业务逻辑
if (typeof arg1 === 'object') {
console.log(`[${arg1.type}] ${arg1.content}`);
} else if (typeof arg2 === 'function') {
console.log(`[info] ${arg1}`);
arg2();
} else if (arg2 && typeof arg3 === 'function') {
console.log(`[${arg2}] ${arg1}`);
arg3();
}
}调用效果 使用时 TS 会精准提示合法参数组合,避免传参错误:
ts
// ✅ 合法调用
message({ content: "操作成功", type: "success" });
message("操作完成", () => console.log("回调执行"));
message("操作失败", "error", () => console.log("错误回调"));
// ❌ 非法调用:TS 直接报错,无此参数组合
message("内容", "error");3、 对象中函数的重载声明
当函数作为对象的属性时,可通过接口声明重载类型。 代码示例
ts
// 1. 定义接口,声明函数重载签名
interface MessageHandler {
// 重载签名
(options: { content: string; type: 'success' | 'error' }): void;
(content: string, callback: () => void): void;
(content: string, type: 'success' | 'error', callback: () => void): void;
}
// 2. 定义对象,指定函数类型为接口
const handler: { showMessage: MessageHandler } = {
showMessage: function(
arg1: string | { content: string; type: 'success' | 'error' },
arg2?: ('success' | 'error') | (() => void),
arg3?: () => void
): void {
// 实现逻辑和上方一致
}
}
// 调用
handler.showMessage({ content: "测试", type: "error" });4、 核心要点
- 函数重载仅作用于编译阶段,运行时不存在重载,最终执- 行的是实现签名的逻辑。
- 重载签名的参数类型,必须被实现签名的参数类型兼容。
- 适合多参数组合、功能灵活的函数,能大幅提升类型提示的友好度。
二、never类型
1、never 类型的含义
never 类型表示不存在、不可能出现的值的类型,用于描述永远不会发生的情况。可以类比汉语里的 “不存在” 一词,专门用来表达 “某个情况不会出现” 的语义。
2、核心实战:switch 语句中利用 never 做完整性检查
这是 never 类型最实用的场景,可以保证当类型扩展时,代码会在编译阶段报错,避免遗漏分支处理。
步骤 1:定义基础类型和函数
ts
// 定义请求方法类型
type HttpMethod = "get" | "post";
// 模拟请求函数
function request(method: HttpMethod, url: string) {
switch (method) {
case "get":
console.log(`发起 GET 请求: ${url}`);
break;
case "post":
console.log(`发起 POST 请求: ${url}`);
break;
default:
// 利用 never 做完整性检查
const _exhaustiveCheck: never = method;
throw new Error(`未知的请求方法: ${_exhaustiveCheck}`);
}
}
// 正常调用
request("get", "/user"); // 发起 GET 请求: /user
request("post", "/login"); // 发起 POST 请求: /login此时 default 分支理论上不会执行,method 类型被收窄为 never。
步骤 2:扩展类型,验证 never 的作用
当我们给 HttpMethod 新增一个 put 类型时:
ts
// 扩展请求方法类型
type HttpMethod = "get" | "post" | "put";
// 此时编译器会报错!
function request(method: HttpMethod, url: string) {
switch (method) {
case "get":
console.log(`发起 GET 请求: ${url}`);
break;
case "post":
console.log(`发起 POST 请求: ${url}`);
break;
default:
// ❌ 报错:类型 "put" 不能赋值给类型 "never"
const _exhaustiveCheck: never = method;
throw new Error(`未知的请求方法: ${_exhaustiveCheck}`);
}
}**报错原因:**新增 put 类型后,default 分支的 method 类型变成了 put,而 put 不能赋值给 never,编译器直接提示我们遗漏了 put 分支的处理。
步骤 3:修复报错,补充分支
ts
type HttpMethod = "get" | "post" | "put";
function request(method: HttpMethod, url: string) {
switch (method) {
case "get":
console.log(`发起 GET 请求: ${url}`);
break;
case "post":
console.log(`发起 POST 请求: ${url}`);
break;
case "put": // 补充 put 分支
console.log(`发起 PUT 请求: ${url}`);
break;
default:
const _exhaustiveCheck: never = method; // 不再报错
throw new Error(`未知的请求方法: ${_exhaustiveCheck}`);
}
}
// 正常调用
request("put", "/update"); // 发起 PUT 请求: /update3、核心亮点总结
- 语义明确: never 专门描述 “不可能发生” 的场景,让代码意图更清晰。
- 编译时检查: 在 switch 中搭配 default 分支使用 never,可以在编译阶段发现遗漏的分支处理,避免运行时出现隐藏 bug。
- 提升可维护性: 当类型扩展时,强制开发者补充对应的逻辑,降低代码维护成本。
