TypeScript函数类型全攻略:从基础约束到高级玩法

TypeScript函数类型全攻略:从基础约束到高级玩法

函数是TypeScript中最核心的复用单元,也是类型约束的高频场景。很多开发者只会给函数加简单的参数/返回值类型,却忽略了可选参数、readonly参数、高阶函数等实用特性。本文会从基础到进阶,把TS函数类型的核心知识点讲透,覆盖日常开发所有场景。

一、基础核心:Function类型与函数类型表达式

TS中描述函数类型有两种核心方式:Function 类型(宽泛)和**函数类型表达式,先分清这两种写法的区别。

1. Function类型(不推荐)

Function 是TS的内置类型,代表"任意函数",但它过于宽泛,会丢失类型约束的意义:

typescript 复制代码
// 宽泛的Function类型:能接收任何函数,但无法约束参数/返回值
let fn: Function;
fn = () => 123; // 任意函数都能赋值
fn = (a: string) => a; //  无类型检查
fn(1, 2, 3); //调用时参数任意,无报错

缺点:无法约束参数个数、类型和返回值,相当于弱化了TS的类型检查,仅适合完全不确定函数结构的极端场景。

2. 函数类型表达式(推荐)

(参数: 类型) => 返回值类型 的语法,精准描述函数结构,是日常开发的首选:

typescript 复制代码
// 定义精准的函数类型
type AddFunc = (a: number, b: number) => number;

// 赋值:必须严格匹配参数和返回值类型
const add: AddFunc = (x, y) => x + y; //  正确
// const add2: AddFunc = (x: string, y: string) => x + y; //  报错:参数类型不匹配

二、参数进阶:可选/默认/解构/rest/只读参数

函数参数的约束是TS函数类型的核心,掌握以下5种参数类型。

1. 可选参数(?)

? 标记参数,表示"可传可不传",必须放在必选参数之后

typescript 复制代码
// message是可选参数
function greet(name: string, message?: string): string {
  return message ? `${message}, ${name}` : `Hello, ${name}`;
}

greet("张三"); //  正确,返回 "Hello, 张三"
greet("李四", "早上好"); //  正确,返回 "早上好, 李四"
// greet(undefined, "Hi"); //  报错:必选参数name不能省略

2. 参数默认值

参数带默认值时,自动成为可选参数,类型会根据默认值推导(也可显式约束):

typescript 复制代码
// count默认值为10,类型自动推导为number
function repeat(str: string, count = 10): string {
  return str.repeat(count);
}

repeat("TS"); //  正确,重复10次
repeat("JS", 5); //  正确,重复5次
// repeat("React", "5"); //  报错:count必须是number

3. 参数解构(对象/数组)

函数参数是对象/数组时,可直接解构并约束类型,让代码更简洁:

typescript 复制代码
// 1. 对象解构 + 类型约束
type User = { name: string; age: number };
function printUser({ name, age }: User): void {
  console.log(`姓名:${name},年龄:${age}`);
}

// 2. 数组解构 + 类型约束
function printArr([a, b]: [number, string]): void {
  console.log(a, b);
}

printUser({ name: "张三", age: 25 }); //  正确
printArr([1, "TS"]); //  正确

4. rest参数(剩余参数)

... 接收多个参数,类型必须是数组,且只能放在参数列表最后:

typescript 复制代码
// 求和函数:接收任意多个数字参数
function sum(...nums: number[]): number {
  return nums.reduce((acc, curr) => acc + curr, 0);
}

sum(1, 2); //  正确,返回3
sum(1, 2, 3, 4); // 正确,返回10
// sum(1, "2"); //  报错:rest参数必须是number[]

5. readonly只读参数

readonly 约束参数(通常是数组/对象),避免函数内部修改入参,提升代码健壮性:

typescript 复制代码
// 约束数组参数为只读,禁止修改
function printArr(arr: readonly number[]): void {
  // arr.push(4); //  报错:readonly数组不能调用push
  console.log(arr);
}

printArr([1, 2, 3]); //  正确

// 约束对象参数的属性为只读
function printObj(obj: { readonly name: string }): void {
  // obj.name = "李四"; //  报错:name是只读属性
  console.log(obj.name);
}

三、返回值进阶:void类型与never类型

TS对函数返回值的约束不止基础类型,voidnever 是两个特殊且常用的返回值类型。

1. void类型:无返回值

函数没有return(或return undefined)时,返回值类型为 void,表示"无任何返回值":

typescript 复制代码
// 纯打印函数,无返回值
function log(msg: string): void {
  console.log(msg);
  // return undefined; // 可选,void允许返回undefined
}

const res = log("Hello TS"); // res的类型为void

注意void 不是 undefined------void 表示"函数无返回值",而 undefined 是一个具体的值;但函数返回 undefined 时,可赋值给 void 类型变量。

2. never类型:永不返回

函数永远不会执行完(抛出错误、无限循环)时,返回值类型为 never

typescript 复制代码
// 场景1:抛出错误的函数
function throwError(msg: string): never {
  throw new Error(msg); // 函数执行到这里终止,永不返回
}

// 场景2:无限循环的函数
function infiniteLoop(): never {
  while (true) {} // 永远不会退出循环
}

// 场景3:穷尽检查(确保switch覆盖所有情况)
type Status = "success" | "error" | "pending";
function handleStatus(status: Status): void {
  switch (status) {
    case "success":
      console.log("成功");
      break;
    case "error":
      console.log("失败");
      break;
    case "pending":
      console.log("加载中");
      break;
    default:
      // 若漏写case,这里会提示:status类型为never,无法赋值
      const _exhaustiveCheck: never = status;
      throw new Error(`未知状态:${_exhaustiveCheck}`);
  }
}

四、函数内的局部类型

TS允许在函数内部定义类型(局部类型),作用域仅限函数内部,避免全局类型污染:

typescript 复制代码
function handleUser() {
  // 函数内的局部类型,外部无法访问
  type User = { name: string; age: number };
  
  const user: User = { name: "张三", age: 25 };
  console.log(user);
}

handleUser();
// const u: User = { name: "李四", age: 30 }; // 报错:User未定义

适用场景:类型仅在当前函数内使用,无需暴露到全局,提升代码封装性。

五、高阶函数:函数作为参数/返回值

高阶函数是"接收函数作为参数"或"返回函数"的函数,TS对高阶函数的类型约束是进阶重点。

1. 函数作为参数

最常见的场景(如数组的map/filter),用函数类型表达式约束入参函数:

typescript 复制代码
// 定义高阶函数:接收数组和处理函数,返回新数组
function mapArr<T, U>(arr: T[], handler: (item: T) => U): U[] {
  return arr.map(handler);
}

// 使用:TS自动推导类型
const nums = [1, 2, 3];
const strNums = mapArr(nums, (n) => n.toString()); // strNums: string[]
const doubles = mapArr(nums, (n) => n * 2); // doubles: number[]

2. 函数作为返回值

返回函数时,用函数类型表达式约束返回值的结构:

typescript 复制代码
// 高阶函数:返回一个加法函数
function createAddFunc(num: number): (x: number) => number {
  return (x) => x + num;
}

// 使用
const add5 = createAddFunc(5);
const res = add5(10); // res: number,值为15

六、函数重载:处理多类型入参/返回值

当函数需要支持"不同参数类型/数量 → 不同返回值类型"时,用函数重载实现精准约束(避免联合类型的模糊性)。

1. 函数重载的基础写法

先写"重载签名"(定义参数-返回值的对应关系),再写"实现签名"(实际逻辑):

typescript 复制代码
// 步骤1:定义重载签名(可写多个)
function formatData(data: string): string; // 入参string → 返回string
function formatData(data: number): number[]; // 入参number → 返回number[]

// 步骤2:定义实现签名
function formatData(data: string | number): string | number[] {
  if (typeof data === "string") {
    return data.toUpperCase(); // 匹配第一个重载
  } else {
    return Array.from(String(data), Number); // 匹配第二个重载
  }
}

// 调用:TS自动推导返回值类型
const strRes = formatData("ts"); // strRes: string
const numRes = formatData(123); // numRes: number[]
// formatData(true); // 报错:无匹配的重载签名

2. 函数重载的注意事项

  • 重载签名只用于类型检查,不会执行,逻辑全部写在实现签名中;
  • 实现签名的参数类型需包含所有重载签名的参数类型;
  • 避免过度重载:简单场景用联合类型即可,重载适合"参数和返回值强关联"的场景。

七、箭头函数的类型约束

箭头函数是TS中最常用的函数形式,其类型约束更简洁,且无需处理this指向问题。

1. 箭头函数的基础类型约束

typescript 复制代码
// 方式1:直接约束参数和返回值
const add = (a: number, b: number): number => a + b;

// 方式2:用类型别名约束(复用场景)
type AddFunc = (a: number, b: number) => number;
const add2: AddFunc = (a, b) => a + b;

2. 箭头函数的this指向(无需约束)

箭头函数没有自己的this,会继承外层作用域的this,TS会自动推导,无需手动约束:

typescript 复制代码
const user = {
  name: "李四",
  age: 30,
  // 箭头函数的this继承自外层(非严格模式下是window)
  sayHi: () => {
    console.log(this.name); // TS提示:this上不存在name属性
  },
  // 普通函数的this指向user实例
  sayHello() {
    console.log(this.name); // 正确,this指向user
  },
};

八、实战避坑指南

1. 避免用any弱化函数类型

typescript 复制代码
//  错误:any导致类型检查失效
function handleData(data: any): any {
  return data;
}

//  正确:用泛型替代any
function handleData<T>(data: T): T {
  return data;
}

2. 可选参数必须放在必选参数后

typescript 复制代码
//  报错:可选参数不能在必选参数前
// function fn(a?: number, b: string): void {}

//  正确
function fn(b: string, a?: number): void {}

3. rest参数只能有一个且放最后

typescript 复制代码
//  报错:rest参数必须是最后一个
// function fn(...nums: number[], a: string): void {}

//  正确
function fn(a: string, ...nums: number[]): void {}

4. 重载签名与实现签名要兼容

typescript 复制代码
//  报错:实现签名的返回值不兼容重载签名
// function fn(a: string): string;
// function fn(a: number): number;
// function fn(a: string | number): string { ... }

//  正确:实现签名返回值包含所有重载返回值
function fn(a: string): string;
function fn(a: number): number;
function fn(a: string | number): string | number { ... }

九、核心总结

  1. 函数类型描述:优先用「函数类型表达式」,避免宽泛的Function类型;
  2. 参数约束:掌握可选参数(?)、默认值、解构、rest参数、readonly参数,覆盖所有参数场景;
  3. 返回值约束:void表示无返回值,never表示永不返回(常用于错误/循环);
  4. 进阶特性:局部类型提升封装性,高阶函数用泛型约束参数/返回值,重载适配多类型入参;
  5. 避坑核心:不用any弱化类型,参数位置(可选/rest)遵守规则,重载签名与实现签名兼容。

掌握这些知识点,你的TS函数类型约束会既精准又灵活,彻底告别"函数类型报错却找不到原因"的问题~

复制代码
相关推荐
再希1 小时前
TypeScript初体验(四)在React中使用TS
javascript·react.js·typescript
EndingCoder1 小时前
函数基础:参数和返回类型
linux·前端·ubuntu·typescript
EndingCoder2 小时前
箭头函数和 this 绑定
linux·前端·javascript·typescript
小二·15 小时前
微前端架构完全指南:qiankun 与 Module Federation 双方案深度对比(Vue 3 + TypeScript)
前端·架构·typescript
EndingCoder15 小时前
枚举类型:常量集合的优雅管理
前端·javascript·typescript
起名时在学Aiifox16 小时前
从零实现前端数据格式化工具:以船员经验数据展示为例
前端·vue.js·typescript·es6
Sun_小杰杰哇17 小时前
Dayjs常用操作使用
开发语言·前端·javascript·typescript·vue·reactjs·anti-design-vue
座山雕~17 小时前
TypeScript语法大全
typescript
Benny的老巢19 小时前
基于Playwright TypeScript/JavaScript的API调用爬虫成熟方案
javascript·爬虫·typescript·自动化·agent·playwright
踢球的打工仔1 天前
typescript-引用和const常量
前端·javascript·typescript