泛型基础

泛型允许我们在定义函数、接口或类时不预先指定具体类型,而是在使用时再指定。这种类型参数化的机制让代码更加灵活和可复用。

类型参数

泛型的核心概念是类型参数,使用 <T> 语法定义。T 是一个占位符,表示某种类型。

function identity<T>(arg: T): T {
  return arg
}

const str = identity<string>("hello")
const num = identity(123)

console.log(typeof str)
console.log(typeof num)

<T> 声明了一个类型参数 T,函数的参数和返回值都使用这个类型。调用时可以显式指定类型,也可以让 TypeScript 自动推断。

多个类型参数

可以定义多个类型参数,用逗号分隔。

function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value]
}

const p1 = pair("name", "张三")
const p2 = pair(1, true)

console.log(p1)
console.log(p2)

pair 函数有两个类型参数 KV,分别表示键和值的类型。返回值是一个元组,包含这两种类型。

类型参数命名

类型参数可以使用任意名称,但有一些常见的约定:

  • T:Type,最常用的泛型参数名
  • K:Key,表示键类型
  • V:Value,表示值类型
  • E:Element,表示元素类型
  • R:Return,表示返回值类型
interface Map<K, V> {
  get(key: K): V | undefined
  set(key: K, value: V): void
}

class MyMap<K, V> implements Map<K, V> {
  private data: Record<string, V> = {}

  get(key: K): V | undefined {
    return this.data[String(key)]
  }

  set(key: K, value: V): void {
    this.data[String(key)] = value
  }
}

const map = new MyMap<string, number>()
map.set("one", 1)
map.set("two", 2)
console.log(map.get("one"))

使用有意义的名称可以让代码更加清晰。

类型参数推断

TypeScript 可以根据传入的参数自动推断类型参数。

function first<T>(arr: T[]): T | undefined {
  return arr[0]
}

const n = first([1, 2, 3])
const s = first(["a", "b", "c"])

console.log(typeof n)
console.log(typeof s)

调用 first 时没有显式指定类型,TypeScript 根据数组元素的类型推断出 T

显式指定类型

有时需要显式指定类型参数,特别是当 TypeScript 无法正确推断时。

function parseJSON<T>(json: string): T {
  return JSON.parse(json)
}

interface User {
  id: number
  name: string
}

const user = parseJSON<User>('{"id": 1, "name": "张三"}')
console.log(user.name)

parseJSON 函数返回值的类型需要显式指定,因为 TypeScript 无法从 JSON 字符串推断出对象类型。

类型参数的作用域

类型参数在声明它的范围内有效。

function wrap<T>(value: T): { value: T } {
  return { value }
}

const wrapped = wrap("hello")
console.log(wrapped.value)

class Container<T> {
  private value: T

  constructor(value: T) {
    this.value = value
  }

  getValue(): T {
    return this.value
  }
}

const container = new Container(123)
console.log(container.getValue())

函数的类型参数在函数内部有效,类的类型参数在整个类中有效。

类型参数与联合类型

类型参数与联合类型是不同的概念。

function process1<T extends string | number>(value: T): T {
  return value
}

function process2(value: string | number): string | number {
  return value
}

const r1 = process1("hello")
const r2 = process2("hello")

console.log(typeof r1)
console.log(typeof r2)

使用泛型时,返回值类型与参数类型相同。使用联合类型时,返回值是联合类型。泛型保留了更精确的类型信息。

类型参数的约束

类型参数可以有约束,限制可以接受的类型范围。

interface Lengthwise {
  length: number
}

function logLength<T extends Lengthwise>(arg: T): number {
  return arg.length
}

console.log(logLength("hello"))
console.log(logLength([1, 2, 3]))
console.log(logLength({ length: 10 }))

T extends Lengthwise 表示 T 必须满足 Lengthwise 接口的要求,即必须有 length 属性。

小结

泛型基础包括类型参数的定义和使用。类型参数是类型的占位符,在使用时确定具体类型。可以定义多个类型参数,TypeScript 可以自动推断或显式指定类型。类型参数可以有约束,限制可接受的类型范围。理解泛型基础是掌握 TypeScript 高级特性的关键。