Skip to content

函数重载、never类型

一、函数重载(overload)

核心背景

日常使用 TS 时,很多人仅做简单类型标注,忽略了函数重载这个实用功能。它能解决多参数组合函数的类型提示不清晰、参数关系不明确的问题,提升代码可读性与开发体验,仅作用于编译阶段,不影响运行逻辑。

1、 常规类型声明的痛点

以一个 message 消息提示函数为例,它支持多种参数组合:

  1. 传入配置对象 {content: string, type: 'success' | 'error'}
  2. 传入字符串 + 回调函数
  3. 传入字符串 + 消息类型 + 回调函数

常规写法的缺陷:

  • 类型提示模糊,无法直观看到参数的合法组合
  • 参数关系不明确,使用者难以判断不同参数的搭配规则
  • 可读性差,类型定义冗长混乱
ts
// 常规写法:参数类型合并,提示不清晰
function message(
  arg1: string | { content: string; type: 'success' | 'error' },
  arg2?: ('success' | 'error') | (() => void),
  arg3?: () => void
): void {
  // 实现逻辑
}

调用时,TS 无法精准提示参数组合,容易传参错误

2、 函数重载的实现

核心思路

  1. 先声明重载签名:罗列函数所有可能的参数组合(只声明,不实现)
  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 请求: /update

3、核心亮点总结

  1. 语义明确: never 专门描述 “不可能发生” 的场景,让代码意图更清晰。
  2. 编译时检查: 在 switch 中搭配 default 分支使用 never,可以在编译阶段发现遗漏的分支处理,避免运行时出现隐藏 bug。
  3. 提升可维护性: 当类型扩展时,强制开发者补充对应的逻辑,降低代码维护成本。

The future is promising