Skip to content

泛型基础

泛型是 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') // string
const num = identity<number>(42) // number
const 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 => arg
const 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 = number
const 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/Parameters
type 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) => arg
const 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') // string
const 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 + b
const 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 推断为 string
const n1 = identity(42) // T 推断为 number
// 复杂推断
function firstElement<T>(arr: T[]): T | undefined {
return arr[0]
}
const first1 = firstElement(['a', 'b', 'c']) // string | undefined
const 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
// 使用时通常只需指定 T
create<string>('hello')
create<string, { debug: boolean }>('hello', { debug: true })

总结#

特性语法用途
类型参数<T>定义泛型占位符
多类型参数<T, U>多个类型关联
默认类型<T = string>提供默认值
类型推断自动减少显式标注
函数类型<T>(arg: T) => T泛型函数签名

下一篇我们将学习泛型接口与泛型类,了解如何在更复杂的场景中使用泛型。