泛型是 TypeScript 中最强大的特性之一,它允许我们创建可重用的组件,这些组件可以支持多种类型而不是单一类型。
为什么需要泛型#
看一个没有泛型的问题:
// TypeScript 5.x
// 问题:如何写一个通用的 identity 函数?
// 方案1:具体类型 - 不通用function identityNumber(arg: number): number { return arg}
function identityString(arg: string): string { return arg}
// 方案2:any - 丢失类型信息function identityAny(arg: any): any { return arg}
const result = identityAny('hello')// result 是 any,丢失了 string 类型
// 方案3:泛型 - 完美解决function identity<T>(arg: T): T { return arg}
const str = identity<string>('hello') // stringconst num = identity<number>(42) // numberconst auto = identity('hello') // 自动推断为 string泛型函数#
基本语法#
// 单个类型参数function identity<T>(arg: T): T { return arg}
// 多个类型参数function pair<T, U>(first: T, second: U): [T, U] { return [first, second]}
const p = pair<string, number>('hello', 42) // [string, number]const p2 = pair('hello', 42) // 自动推断
// 使用类型参数约束返回值function map<T, U>(arr: T[], fn: (item: T) => U): U[] { return arr.map(fn)}
const numbers = [1, 2, 3]const strings = map(numbers, (n) => n.toString()) // string[]箭头函数泛型#
// 普通箭头函数const identity1 = <T>(arg: T): T => arg
// 在 .tsx 文件中,需要加逗号或 extends 避免与 JSX 冲突const identity2 = <T>(arg: T): T => argconst identity3 = <T extends unknown>(arg: T): T => arg
// 多参数const swap = <T, U>(tuple: [T, U]): [U, T] => { return [tuple[1], tuple[0]]}
const swapped = swap(['hello', 42]) // [number, string]类型参数默认值#
// 默认类型参数function createArray<T = string>(length: number, value: T): T[] { return Array(length).fill(value)}
const strings = createArray(3, 'hello') // string[]const numbers = createArray<number>(3, 42) // number[]const defaultStrings = createArray(3, 'hi') // string[]
// 多个默认值interface Options<T = string, U = number> { value: T count: U}
const opt1: Options = { value: 'hello', count: 1 } // 使用默认类型const opt2: Options<boolean> = { value: true, count: 1 } // T = boolean, U = numberconst opt3: Options<boolean, string> = { value: true, count: 'one' }类型变量命名约定#
常见的类型变量名:
// T - Type,最常用function identity<T>(arg: T): T
// K, V - Key, Value,用于键值对function getProperty<K, V>(obj: Record<K, V>, key: K): V
// E - Element,用于元素function first<E>(arr: E[]): E | undefined
// R - Return,用于返回值function wrap<T, R>(fn: (arg: T) => R): (arg: T) => R
// P - Props/Parameterstype ComponentProps<P> = { props: P }
// 描述性命名(复杂场景)function transform<TInput, TOutput>( input: TInput, fn: (value: TInput) => TOutput): TOutput使用类型参数的数组#
function loggingIdentity<T>(arg: T[]): T[] { console.log(arg.length) // 数组有 length 属性 return arg}
// 等价写法function loggingIdentity2<T>(arg: Array<T>): Array<T> { console.log(arg.length) return arg}
// 实际应用function first<T>(arr: T[]): T | undefined { return arr[0]}
function last<T>(arr: T[]): T | undefined { return arr[arr.length - 1]}
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] { return arr.filter(predicate)}
const nums = [1, 2, 3, 4, 5]const evens = filter(nums, (n) => n % 2 === 0) // number[]泛型函数类型#
// 函数类型表达式type IdentityFn = <T>(arg: T) => T
const identity: IdentityFn = (arg) => arg
// 带调用签名的对象类型type IdentityObj = { <T>(arg: T): T}
// 类型参数在外层type GenericIdentityFn<T> = (arg: T) => T
const stringIdentity: GenericIdentityFn<string> = (arg) => argconst numberIdentity: GenericIdentityFn<number> = (arg) => arg常见泛型函数模式#
获取对象属性#
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]}
const user = { name: '张三', age: 25, email: 'test@test.com',}
const name = getProperty(user, 'name') // stringconst age = getProperty(user, 'age') // number// getProperty(user, 'invalid'); // ❌ 错误合并对象#
function merge<T, U>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 }}
const merged = merge({ name: '张三' }, { age: 25 })// { name: string; age: number }console.log(merged.name, merged.age)包装函数#
function withLogging<T extends (...args: any[]) => any>(fn: T): T { return ((...args: Parameters<T>): ReturnType<T> => { console.log('Calling function with:', args) const result = fn(...args) console.log('Result:', result) return result }) as T}
const add = (a: number, b: number) => a + bconst loggedAdd = withLogging(add)
loggedAdd(1, 2) // 输出日志,返回 3创建工厂函数#
function createFactory<T>(type: new () => T): () => T { return () => new type()}
class User { name = 'default'}
const createUser = createFactory(User)const user = createUser() // User类型推断#
TypeScript 可以从参数推断类型参数:
function identity<T>(arg: T): T { return arg}
// 显式指定const s1 = identity<string>('hello')
// 自动推断const s2 = identity('hello') // T 推断为 stringconst n1 = identity(42) // T 推断为 number
// 复杂推断function firstElement<T>(arr: T[]): T | undefined { return arr[0]}
const first1 = firstElement(['a', 'b', 'c']) // string | undefinedconst first2 = firstElement([1, 2, 3]) // number | undefined
// 字面量推断const first3 = firstElement(['a', 'b'] as const)// "a" | "b" | undefined常见问题#
🙋 泛型和 any 有什么区别?#
// any:丢失类型信息function anyIdentity(arg: any): any { return arg}const a = anyIdentity('hello')a.toUpperCase() // any,无类型检查
// 泛型:保留类型信息function genericIdentity<T>(arg: T): T { return arg}const g = genericIdentity('hello')g.toUpperCase() // string,有类型检查🙋 何时使用泛型?#
- 函数参数和返回值类型有关联
- 需要处理多种类型
- 想保持类型安全的同时复用代码
// ✅ 适合泛型:输入输出类型关联function identity<T>(arg: T): T { return arg}
// ❌ 不需要泛型:没有类型关联function greet(name: string): void { console.log(`Hello, ${name}`)}🙋 多个类型参数应该什么顺序?#
按使用频率排序,最常需要指定的放前面:
// 好:常用的 T 在前function create<T, Options = {}>(value: T, options?: Options): T
// 使用时通常只需指定 Tcreate<string>('hello')create<string, { debug: boolean }>('hello', { debug: true })总结#
| 特性 | 语法 | 用途 |
|---|---|---|
| 类型参数 | <T> | 定义泛型占位符 |
| 多类型参数 | <T, U> | 多个类型关联 |
| 默认类型 | <T = string> | 提供默认值 |
| 类型推断 | 自动 | 减少显式标注 |
| 函数类型 | <T>(arg: T) => T | 泛型函数签名 |
下一篇我们将学习泛型接口与泛型类,了解如何在更复杂的场景中使用泛型。