interface & type
两者都能定义对象结构和函数签名,但interface 更偏向结构扩展与 OOP 语义;type 更偏向类型组合与类型表达式。
在工程中,interface 用于描述数据结构,type 用于类型运算和复杂组合。
相同点
- 都能描述对象形状
- 都能做函数类型定义
- 都支持泛型
- 都能被 class implements
interface A { x: number }
type B = { x: number }
核心区别
- interface 可以合并声明
interface User { id: number }
interface User { name: string }
const u: User = { id: 1, name: 'a' }
👉 type 不行,这是 interface 最重要的能力。
适合用来扩展第三方库定义(例如扩展 Express Request)。
- type 更灵活,支持类型表达式
type 可以:
- 联合
- 交叉
- 条件类型
- 映射类型
- 模板字符串类型
type Status = 'success' | 'error';
type WithId<T> = T & { id: string };
type Api<T> = T extends any[] ? 'array' : 'object';
👉 interface 做不到这些。
- interface 更适合建模“对象、类、API”结构
interface Person {
name: string;
say(): void;
}
它能:
- 扩展其他 interface(extends)
- 被类 implements(非常 OOP)
- type 可以“别名化”(alias)任何类型
type Fn = (x: number) => void;
type Point = [number, number];
type Maybe<T> = T | undefined;
👉 type 的使用范围更广,甚至可以 alias 基本类型。
interface 做不到。
- 扩展方式不同(extends vs 交叉类型)
- interface 扩展
interface A { x: number }
interface B extends A { y: number }
- type 扩展
type A = { x: number }
type B = A & { y: number }
两者效果一致,但交叉类型能做更多复杂组合。
- 复杂类型构造:type 更强
前端工程里的工具类型、复杂泛型、联合类型转换 几乎都是 type 实现的。
type Partial<T> = {
[P in keyof T]?: T[P];
}
interface 无法实现这种类型运算。
工程中如何选择
如果是 描述数据结构、业务对象、class API,优先用 interface,因为其更可扩展、可合并,团队协作更友好。
如果是 类型组合、联合类型、工具类型、映射类型、模板字面量,一定用 type,因为更灵活。
unknow & any & never
🐱unknown
unknown 是安全的 any,可以接受任何值,但不能被直接使用,需要类型收窄。适用于以下的严格使用场景。
- 不信任来源的数据(API、用户输入、动态数据)
function parse(json: string): unknown {
return JSON.parse(json);
}
- 泛型上界不确定,但不希望失去类型安全
function handle<T extends unknown>(value: T) {}
- 设计库时需要“类型安全的扩展点”
比如写 SDK、插件系统时,unknown 让调用方必须显式确认类型。
🐶never
never 代表“不可能发生的值”(类型系统的底部类型)
- 程序不会走到这里
- 函数不会返回
- 分支被完全排除
适用于以下的严格使用场景。
- 表示函数永远不返回(异常 / 死循环)
function fail(msg: string): never {
throw new Error(msg);
}
- 类型收窄后出现“不可能的情况”
function assertNever(x: never): never {
throw new Error("Unexpected value: " + x);
}
配合基础类型保护写 exhaustiveness check:
type Shape = 'circle' | 'square';
function area(s: Shape) {
switch (s) {
case 'circle': return 1;
case 'square': return 1;
default:
return assertNever(s); // 编译期报错,保证分支覆盖完整
}
}
- 联合类型排除成员
type Exclude<T, U> = T extends U ? never : T;
“never 用于严格的编译期检查、不可达代码、以及保证联合类型逻辑完备性。”
🐯any
any 会关闭所有类型检查,是 TS 世界的“丧失类型安全”。适用于以下严格使用场景(只有这些情况能用)
- 渐进式迁移 JS → TS,需要暂时兜底
let temp: any = legacyLib.getData();
- 类型真的无法确定(第三方库,动态字段特别复杂)
比如老旧 SDK,或者大量动态 key。
- 需要与 JS 环境交互(特殊全局变量、动态属性)
例如 window 全局写入自定义字段。
- 为了避免过度复杂的类型推导(工程权衡)
例如复杂泛型导致 IDE 卡顿。
any 是一种工程妥协,不是类型,它是关闭类型检查的开关。只有在迁移、兼容、动态环境时才应该使用。
三者关系
- unknown 是安全的顶类型(不信任输入 → 先 unknown)
- never 是不可能发生的底类型(穷尽检查、异常、严格逻辑)
- any 是逃生舱(完全关闭类型检查,只在必要时使用)
unknown 与 any 是对立的:一个提高安全,一个降低安全;
never 则代表类型系统的底部,是逻辑完备性检查的核心工具。
Partial & Pick & infer
Partial 和 Pick 是典型的映射类型,用来‘改造’对象键;
infer 是在条件类型中做‘反向推断’,固定在提取某些类型信息的场景里。
这三个是 TypeScript 类型系统里最基础也最常用的构建块,理解它们能写出真正类型安全的库级代码。”
🍒Partial
Partial<T> 会把类型 T 的所有属性变成可选属性。
它是一个典型的「映射类型」,通过遍历 keyof T,把每个键的属性加上 ? 修饰符。
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
工程使用场景:
- 表单场景:编辑时部分字段可选
- DTO/Object patch:只更新传入字段
- 配置对象:允许用户覆盖默认配置
生产里基本把所有配置类型都写成 Partial 形式,减少冗余类型声明,也避免用户必须传所有属性。
🍑Pick
Pick<T, K> 从类型 T 中挑选部分键 K,生成一个子类型。
它同样是映射类型,只是遍历 K 而不是 keyof T。
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
工程使用场景
- API 拆分:只需要某些字段
- React Props 提取
- 二次封装组件时抽取部分属性
Pick 本质上就是结构化编程里的投影操作,非常常用。
🍇infer
infer 是 TypeScript 在条件类型里用于声明一个待推断的类型变量。
它让 TypeScript 能“从类型中反向提取信息”。
- 只能在条件类型
T extends X ? ... : ...的true分支里使用 - 相当于告诉 TS:“这里有个未知类型 R,你帮我推断它是什么”
- 最典型的例子:从函数类型中提取返回值
手写 ReturnType
type MyReturnType<F> =
F extends (...args: any[]) => infer R
? R
: never;
常见 infer 使用:
- 提取 Promise 内部类型
type Awaited<T> = T extends Promise<infer R> ? R : T;
- 提取数组元素类型
type ElementOf<T> = T extends (infer U)[] ? U : T;
工程使用场景
- 自动根据 API 类型生成 Response 类型
- 根据函数库自动推断返回值
- 在前端状态管理中自动生成 Action 类型
- 在复杂表单里从 DTO 推类型
infer 是 TypeScript 高阶类型里很核心的机制,它让 TypeScript 能像编译器一样进行模式匹配。
as 断言
简单来说,as断言就是告诉TS编译器:"我知道这个变量是什么类型,请相信我!"
// 基础语法
值 as 类型
// 或者
<类型>值 // 不常用,避免和JSX混淆
🎯 使用场景
1️⃣ DOM元素操作(最常用!)
// 不这样写
const input = document.getElementById('input')
input.value // ❌ TS报错:对象可能为null
// 正确姿势 ✅
const input = document.getElementById('input') as HTMLInputElement
input.value // 搞定!
2️⃣ 联合类型的窄化
interface Cat {
meow: () => void
name: string
}
interface Dog {
bark: () => void
age: number
}
function handlePet(pet: Cat | Dog) {
(pet as Cat).meow() // 断言为Cat类型
}
3️⃣ 处理any类型
const response: any = fetchData()
const data = response as UserData // 明确告诉TS这是UserData
⚠️ 避坑指南!
❌ 错误的断言
const num: string = '123'
const bool = num as boolean
// 报错!string和boolean互不兼容
❌ 双重断言
const str = 'hello'
const num = str as unknown as number // 可以,但危险!
当两个类型完全不同,但你确定数据类型时,可以先转unknown/any
💡 使用建议
🔥 对比类型守卫:联合类型下优先采用类型守卫而不是断言!
// 不推荐:滥用as
function process(val: string | number) {
(val as string).toUpperCase() // 危险!
}
// 推荐:类型守卫 ✅
function process(val: string | number) {
if (typeof val === 'string') {
val.toUpperCase() // 类型已窄化
}
}
🎨 与as const的区别
const str1 = 'hello' // 类型:string
const str2 = 'hello' as const // 类型:'hello' 字面量
const arr1 = [1, 2] // 类型:number[]
const arr2 = [1, 2] as const // 类型:readonly [1, 2]
- 1️⃣ 能不用的地方尽量不用 - 让TS自己推断
- 2️⃣ 操作DOM时放心用 - 这是最安全的场景
- 3️⃣ API返回数据谨慎用 - 最好配合校验
- 4️⃣ 类型守卫优先 - 比as更安全
Q:"as断言会改变运行时类型吗?" A:不会! as只在编译时起作用,运行时类型由实际值决定
const data: any = 123
const str = data as string
console.log(typeof str) // 运行时还是 "number"!
as断言就像TS的"强制类型转换",用好了能救命,用不好会背锅!
类型谓词
类型谓词就是自定义类型守卫的返回值语法,格式:参数名 is 类型
function isFish(animal: Bird | Fish): animal is Fish {
return (animal as Fish).swim !== undefined
}
// 👆 这就是类型谓词!
基础语法解析
function 函数名(参数: 待检查的类型): 参数 is 目标类型 {
// 返回boolean
// true表示参数是目标类型
// false表示参数不是目标类型
}
为什么需要类型谓词?
// ❌ 没有类型谓词的情况
function isFish(animal: Bird | Fish): boolean {
return (animal as Fish).swim !== undefined
}
function move(animal: Bird | Fish) {
if (isFish(animal)) {
animal.swim() // ❌ 报错!虽然结果是true,但是TS还是不知道animal是Fish
}
}
// ✅ 有了类型谓词
function isFish(animal: Bird | Fish): animal is Fish {
return (animal as Fish).swim !== undefined
}
function move(animal: Bird | Fish) {
if (isFish(animal)) {
animal.swim() // ✅ 完美!TS知道是Fish
}
}
🎨 类型谓词的各种妙用
1️⃣ 基础类型检查
// 检查是否为字符串
function isString(value: unknown): value is string {
return typeof value === 'string'
}
// 检查是否为数字
function isNumber(value: unknown): value is number {
return typeof value === 'number' && !isNaN(value)
}
function process(value: unknown) {
if (isString(value)) {
return value.toUpperCase() // string
}
if (isNumber(value)) {
return value.toFixed(2) // number
}
}
2️⃣ 复杂对象验证
interface Article {
id: number
title: string
content: string
tags?: string[]
}
function isArticle(obj: any): obj is Article {
return (
obj !== null &&
typeof obj === 'object' &&
typeof obj.id === 'number' &&
typeof obj.title === 'string' &&
typeof obj.content === 'string' &&
(obj.tags === undefined ||
Array.isArray(obj.tags) &&
obj.tags.every((tag: any) => typeof tag === 'string'))
)
}
// 安全地使用API返回数据
async function getArticle(id: number) {
const res = await fetch(`/api/articles/${id}`)
const data = await res.json()
if (isArticle(data)) {
return data // data被正确识别为Article
}
throw new Error('Invalid article data')
}
3️⃣ 数组类型守卫
// 检查数组中的每个元素
function isNumberArray(arr: any): arr is number[] {
return Array.isArray(arr) && arr.every(item => typeof item === 'number')
}
function sum(values: unknown) {
if (isNumberArray(values)) {
return values.reduce((a, b) => a + b, 0) // values是number[]
}
return 0
}
4️⃣ 联合类型窄化
type Success<T> = { status: 'success'; data: T }
type Failure = { status: 'error'; error: string }
type Result<T> = Success<T> | Failure
// 类型谓词判断成功状态
function isSuccess<T>(result: Result<T>): result is Success<T> {
return result.status === 'success'
}
function handleResult<T>(result: Result<T>) {
if (isSuccess(result)) {
console.log(result.data) // 正确识别为Success<T>
} else {
console.log(result.error) // 正确识别为Failure
}
}
💡 类型谓词 vs 类型断言
// 类型断言 - 你自己保证
const data = response as User // "相信我,这是User"
// 类型谓词 - 函数保证
function isUser(data: any): data is User {
return data && typeof data.name === 'string'
}
if (isUser(response)) {
const data = response // "验证过了,安全使用"
}
// 类型谓词 - 返回boolean
function isPositive(num: any): num is number {
return typeof num === 'number' && num > 0
}
// 断言函数 - 抛出错误
function assertIsPositive(num: any): asserts num is number {
if (typeof num !== 'number' || num <= 0) {
throw new Error('Not a positive number!')
}
}
function processValue(value: any) {
// 类型谓词:安全分支
if (isPositive(value)) {
return Math.sqrt(value) // value是正数
}
// 断言函数:直接使用(确信不会错)
assertIsPositive(value)
return Math.sqrt(value) // value也是正数
}
🚀 高级技巧:多参数类型谓词
// 检查两个值是否类型兼容
function areStrings(a: unknown, b: unknown): a is string {
return typeof a === 'string' && typeof b === 'string'
}
function concatenate(a: unknown, b: unknown) {
if (areStrings(a, b)) {
return a + b // a和b都被识别为string
}
return null
}
Q:如何实现一个可以检查对象是否包含特定属性的类型谓词?
// 进阶:泛型类型谓词
function hasProperty<T extends object, K extends PropertyKey>(
obj: T,
prop: K
): obj is T & Record<K, unknown> {
return prop in obj
}
interface User {
name: string
age?: number
}
function processUser(user: User) {
if (hasProperty(user, 'age')) {
console.log(user.age) // 现在age一定存在!
}
}
类型谓词的核心价值:
- 类型安全:运行时验证 + 编译时推导
- 代码复用:封装复杂的类型检查逻辑
- 可组合性:多个谓词可以组合使用
使用场景:
- ✅ API响应数据验证
- ✅ 表单输入验证
- ✅ 复杂对象类型检查
- ✅ 联合类型窄化
- ❌ 简单的typeof检查(直接用typeof守卫)
类型守卫
类型守卫就是在运行时检查类型,让TS在特定作用域内推断出更精确的类型!
function process(value: string | number) {
if (typeof value === 'string') {
// 这里value自动推断为string类型
return value.toUpperCase()
}
// 这里value自动推断为number类型
return value.toFixed(2)
}
🎯 4种类型守卫写法
1️⃣ typeof守卫(基础但常用)
function format(value: string | number | boolean) {
if (typeof value === 'string') {
return `"${value}"` // TS知道这里是string
}
if (typeof value === 'number') {
return value.toFixed(2) // number类型
}
return value ? 'true' : 'false' // boolean类型
}
⚠️ 注意:typeof只能识别基础类型:
string、number、booleansymbol、bigintundefined、function- 不能识别:数组、对象、null
2️⃣ instanceof守卫(面向对象必备)
class Admin {
role: 'admin' = 'admin'
permissions: string[] = []
}
class User {
role: 'user' = 'user'
visits: number = 0
}
function handleUser(user: Admin | User) {
if (user instanceof Admin) {
return user.permissions // 识别为Admin
}
return user.visits // 识别为User
}
3️⃣ in守卫(检查属性是否存在)
interface Bird {
fly: () => void
layEggs: () => void
}
interface Fish {
swim: () => void
layEggs: () => void
}
function move(animal: Bird | Fish) {
if ('fly' in animal) {
return animal.fly() // Bird类型
}
return animal.swim() // Fish类型
}
4️⃣ 自定义类型守卫(🌟)
// 定义类型守卫
function isFish(animal: Bird | Fish): animal is Fish {
return (animal as Fish).swim !== undefined
}
function process(animal: Bird | Fish) {
if (isFish(animal)) {
animal.swim() // ✅ TS识别为Fish
} else {
animal.fly() // ✅ TS识别为Bird
}
}
🔥 实战案例:复杂的类型守卫
📦 区分数组和对象
function processData(data: Record<string, any> | any[]) {
// 自定义守卫判断数组
if (Array.isArray(data)) {
return data.map(item => item) // 识别为数组
}
// 或者判断对象
if (data && typeof data === 'object' && !Array.isArray(data)) {
return Object.keys(data) // 识别为对象
}
}
🎨 区分不同类型的对象
interface Circle {
kind: 'circle'
radius: number
}
interface Rectangle {
kind: 'rectangle'
width: number
height: number
}
type Shape = Circle | Rectangle
// 可辨识联合(Discriminated Union)- 最优雅的方式!
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2 // Circle类型
case 'rectangle':
return shape.width * shape.height // Rectangle类型
}
}
💡 类型守卫 vs as断言
| 对比 | 类型守卫 | as断言 |
|---|---|---|
| 安全性 | ✅ 运行时检查 | ❌ 编译时欺骗 |
| 代码提示 | ✅ 完美支持 | ✅ 完美支持 |
| 使用场景 | 不确定类型时 | 确定类型时 |
| 学习成本 | 低 | 更低 |
| 推荐指数 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
🌟 高级技巧:断言函数
// 断言函数 - 如果错误直接抛出异常
function assertIsNumber(value: any): asserts value is number {
if (typeof value !== 'number') {
throw new Error('Not a number!')
}
}
function double(value: any) {
assertIsNumber(value) // 断言通过后,value就是number
return value * 2 // ✅ 不用as也能正确推断
}
Q:如何实现一个类型守卫判断某个值是否是Promise?
function isPromise<T = any>(value: any): value is Promise<T> {
return value && typeof value.then === 'function'
}
// 使用
if (isPromise(data)) {
data.then(res => console.log(res)) // 识别为Promise
}
extends
extends在TS中有多种身份:
- 类继承(面向对象)
- 接口扩展(类型组合)
- 泛型约束(类型限制)
- 条件类型(类型三目运算)
// 1️⃣ 类继承
class Animal {
eat() {}
}
class Dog extends Animal {
bark() {}
}
// 2️⃣ 接口扩展
interface Person {
name: string
}
interface Employee extends Person {
salary: number
}
// 3️⃣ 泛型约束
function logLength<T extends { length: number }>(arg: T) {
console.log(arg.length)
}
// 4️⃣ 条件类型
type IsArray<T> = T extends any[] ? true : false
🎯 四大应用场景详解
1️⃣ 类继承(OOP基础)
class Animal {
constructor(public name: string) {}
move(distance: number = 0) {
console.log(`${this.name} moved ${distance}m`)
}
}
class Dog extends Animal {
constructor(name: string, public breed: string) {
super(name) // 必须调用super()
}
bark() {
console.log('Woof! Woof!')
}
// 重写方法
move(distance: number = 5) {
console.log('Dog is running...')
super.move(distance)
}
}
const dog = new Dog('Buddy', 'Golden')
dog.bark() // Woof! Woof!
dog.move() // Dog is running... Buddy moved 5m
2️⃣ 接口扩展(组合类型)
// 基础接口
interface BasicInfo {
name: string
age: number
}
// 扩展接口
interface Address {
street: string
city: string
}
// 多接口扩展
interface Person extends BasicInfo, Address {
email: string
phone?: string
}
// 使用示例
const person: Person = {
name: 'Alice',
age: 25,
street: '123 Main St',
city: 'Beijing',
email: 'alice@example.com'
}
// 接口扩展类(高级用法)
class Animal {
type: string = 'mammal'
}
interface Dog extends Animal {
bark(): void
}
class Husky implements Dog {
type: string = 'mammal'
bark() { console.log('Woof!') }
}
3️⃣ 泛型约束
// 基本约束
function getLength<T extends { length: number }>(arg: T): number {
return arg.length
}
getLength('hello') // ✅ string有length
getLength([1, 2, 3]) // ✅ array有length
// getLength(123) // ❌ number没有length
// 约束必须有特定属性
interface HasName {
name: string
}
function greet<T extends HasName>(entity: T) {
console.log(`Hello, ${entity.name}!`)
}
greet({ name: 'Alice', age: 25 }) // ✅
// greet({ age: 25 }) // ❌ 缺少name
// 多重约束(用交叉类型)
function process<T extends HasName & { age: number }>(obj: T) {
console.log(`${obj.name} is ${obj.age} years old`)
}
// 约束key的类型
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key] // 返回类型 T[K]
}
const user = { name: 'Alice', age: 25, email: 'alice@example.com' }
getProperty(user, 'name') // ✅
// getProperty(user, 'gender') // ❌ gender不是key
4️⃣ 条件类型(TypeScript 2.8+)
// 基础条件类型
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
// 分布式条件类型(重点!)
type ToArray<T> = T extends any ? T[] : never
type StrOrNumArr = ToArray<string | number> // string[] | number[]
// 内置条件类型
type NonNullable<T> = T extends null | undefined ? never : T
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
// 实战:深读只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}
interface User {
name: string
address: {
city: string
street: string
}
}
type ReadonlyUser = DeepReadonly<User>
// 所有属性都变成readonly!
💡 extends vs 其他操作符
🔥 extends vs implements
interface Flyable {
fly(): void
}
// extends:继承实现
class Bird {
layEggs() {}
}
class Eagle extends Bird implements Flyable {
fly() {} // 必须实现Flyable
// layEggs() 已经继承
}
// implements:只约束类型
class Airplane implements Flyable {
fly() {} // 只需要实现接口
// 不需要继承任何东西
}
🔥 extends in type vs interface
// interface只能扩展对象类型
interface Person {
name: string
}
interface Employee extends Person {
salary: number
}
// type可以使用条件类型
type IsNumber<T> = T extends number ? true : false
// type可以扩展联合类型
type Animal = 'dog' | 'cat' | 'bird'
type Pet = Animal extends 'dog' ? 'good boy' : Animal
🚀 高级技巧:条件类型的实际应用
1️⃣ 排除特定类型
type Exclude<T, U> = T extends U ? never : T
type T1 = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
type T2 = Exclude<string | number | boolean, number> // string | boolean
2️⃣ 提取特定类型
type Extract<T, U> = T extends U ? T : never
type T3 = Extract<'a' | 'b' | 'c', 'a' | 'b'> // 'a' | 'b'
type T4 = Extract<string | number | boolean, Function> // never
3️⃣ 函数类型提取
// 提取函数参数类型
type FirstArgument<T> = T extends (arg: infer A, ...args: any[]) => any ? A : never
type Fn = (name: string, age: number) => void
type Arg = FirstArgument<Fn> // string
// 提取Promise返回值
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
type PromiseType = UnwrapPromise<Promise<string>> // string
type NormalType = UnwrapPromise<number> // number
Q1:extends在条件类型中的分布式行为是什么意思?
// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never
// 当T是联合类型时,会分别计算每个类型
type Result = ToArray<string | number>
// 等价于:string[] | number[]
// 而不是:(string | number)[]
// 如果想禁止分布式,用方括号包起来
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never
type Result2 = ToArrayNonDist<string | number> // (string | number)[]
Q2:如何实现Pick类型?
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
interface User {
name: string
age: number
email: string
}
type UserNameAndAge = MyPick<User, 'name' | 'age'>
// 结果:{ name: string; age: number; }
Q3:如何实现递归部分可选?
type PartialDeep<T> = T extends object ? {
[P in keyof T]?: PartialDeep<T[P]>
} : T
interface User {
name: string
address: {
city: string
street: string
}
}
type PartialUser = PartialDeep<User>
// 所有属性都变成可选,包括嵌套的!
组合技巧:
extends+keyof= 安全的属性访问extends+infer= 类型提取extends+ 联合类型 = 分布式条件类型
函数重载
函数重载是指同一个函数名,不同参数类型或数量的多个定义!
// 函数重载的写法
function greet(name: string): string
function greet(age: number): string
function greet(value: string | number): string {
if (typeof value === 'string') {
return `Hello, ${value}!`
}
return `You are ${value} years old`
}
greet('Alice') // ✅ Hello, Alice!
greet(25) // ✅ You are 25 years old
🎯 函数重载的两种形式
1️⃣ 基础重载(多参数组合)
// 重载签名
function add(a: number, b: number): number
function add(a: string, b: string): string
function add(a: any, b: any): any {
return a + b
}
add(1, 2) // ✅ 3
add('1', '2') // ✅ '12'
// add(1, '2') // ❌ 没有匹配的重载
2️⃣ 可选参数重载
// 重载签名
function format(value: string): string
function format(value: string, uppercase: boolean): string
function format(value: string, uppercase?: boolean): string {
return uppercase ? value.toUpperCase() : value
}
format('hello') // ✅ 'hello'
format('hello', true) // ✅ 'HELLO'
🔥 函数重载 vs 联合类型
// 方式1:联合类型(简单场景)
function double(value: string | number): string | number {
if (typeof value === 'string') {
return value + value
}
return value * 2
}
const result = double('hi') // 类型:string | number ❌ 不精确
// 方式2:函数重载(精确类型)
function double(value: string): string
function double(value: number): number
function double(value: string | number): string | number {
if (typeof value === 'string') {
return value + value
}
return value * 2
}
const str = double('hi') // 类型:string ✅ 精确!
const num = double(5) // 类型:number ✅ 精确!
🆚 函数重载 vs 泛型
// 泛型:类型参数化
function identity<T>(arg: T): T {
return arg
}
identity<string>('hello') // 返回string
identity<number>(123) // 返回number
| 维度 | 函数重载 | 泛型 |
|---|---|---|
| 适用场景 | 固定几种类型组合 | 类型之间有关联 |
| 灵活性 | 低(要列出所有情况) | 高(任意类型) |
| 精确度 | 极高(可指定返回类型) | 高(保持关系) |
| 维护成本 | 高(类型多了难维护) | 低(一个搞定) |
// 场景1:不同类型不同逻辑(适合重载)
function process(input: string): string[]
function process(input: number): number
function process(input: string | number): string[] | number {
if (typeof input === 'string') {
return input.split('')
}
return input * 2
}
// 场景2:类型关系保持一致(适合泛型)
function wrapInArray<T>(value: T): T[] {
return [value]
}
wrapInArray('hello') // string[]
wrapInArray(123) // number[]
💡 实现一个reverse函数
// 需求:输入字符串返回字符串,输入数组返回数组
// 解法1:函数重载
function reverse(value: string): string
function reverse<T>(value: T[]): T[]
function reverse(value: string | any[]): string | any[] {
if (typeof value === 'string') {
return value.split('').reverse().join('')
}
return value.reverse()
}
// 解法2:泛型(更优雅!)
function reverse<T extends string | any[]>(value: T): T {
if (typeof value === 'string') {
return value.split('').reverse().join('') as T
}
return value.reverse() as T
}
// 解法3:条件类型(最精确!)
type ReverseReturnType<T> = T extends string ? string : T extends any[] ? T : never
function reverse<T extends string | any[]>(value: T): ReverseReturnType<T> {
if (typeof value === 'string') {
return value.split('').reverse().join('') as ReverseReturnType<T>
}
return value.reverse() as ReverseReturnType<T>
}
Q1:函数重载的解析顺序是怎样的?
function foo(x: string): string
function foo(x: number): number
function foo(x: any): any {
return x
}
// TS会按顺序匹配第一个匹配的重载
foo('hello') // 匹配第一个
foo(123) // 匹配第二个
foo(true) // ❌ 没有匹配,报错
Q2:如何实现一个可以接受不同参数个数的函数?
// 需求:sum(1,2) => 3; sum(1,2,3) => 6
// 解法:使用rest参数 + 重载
function sum(a: number, b: number): number
function sum(a: number, b: number, c: number): number
function sum(...args: number[]): number {
return args.reduce((acc, curr) => acc + curr, 0)
}
sum(1, 2) // ✅ 3
sum(1, 2, 3) // ✅ 6
// sum(1) // ❌ 没有匹配
Q3:重载和可选参数的区别?
// 可选参数版本
function greet(name: string, age?: number): string {
return age ? `${name} is ${age}` : `Hello ${name}`
}
// 重载版本
function greet(name: string): string
function greet(name: string, age: number): string
function greet(name: string, age?: number): string {
return age ? `${name} is ${age}` : `Hello ${name}`
}
// 区别:重载可以定义不同的返回类型!
function process(input: string): string[]
function process(input: number): number
// 可选参数做不到返回类型不同
函数重载的核心价值:
- 精确类型:不同参数返回不同类型
- 类型安全:限制非法参数组合
- 文档作用:清晰展示函数用法