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对函数返回值的约束不止基础类型,void 和 never 是两个特殊且常用的返回值类型。
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 { ... }
九、核心总结
- 函数类型描述:优先用「函数类型表达式」,避免宽泛的
Function类型; - 参数约束:掌握可选参数(?)、默认值、解构、rest参数、readonly参数,覆盖所有参数场景;
- 返回值约束:
void表示无返回值,never表示永不返回(常用于错误/循环); - 进阶特性:局部类型提升封装性,高阶函数用泛型约束参数/返回值,重载适配多类型入参;
- 避坑核心:不用any弱化类型,参数位置(可选/rest)遵守规则,重载签名与实现签名兼容。
掌握这些知识点,你的TS函数类型约束会既精准又灵活,彻底告别"函数类型报错却找不到原因"的问题~