泛型

泛型是 TypeScript 中实现代码复用的重要工具。它允许我们编写可以处理多种类型的代码,同时保持类型安全。泛型在函数、接口、类中都有广泛应用。

什么是泛型

泛型是一种类型参数化机制,让类型像函数参数一样可以传递。通过泛型,我们可以创建可重用的组件,它们可以工作于多种类型而不是单一类型。

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

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

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

identity 函数使用了泛型 T,可以接受任意类型的参数并返回相同类型的值。<T> 是类型参数,调用时可以显式指定或让 TypeScript 自动推断。

泛型的优势

泛型的主要优势是代码复用和类型安全。

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

const firstNumber = getFirstElement([1, 2, 3])
const firstString = getFirstElement(["a", "b", "c"])

console.log(firstNumber)
console.log(firstString)

不使用泛型的话,我们需要为每种类型写一个函数,或者使用 any 类型。泛型让我们可以写一个函数处理所有类型,同时保持类型推断。

泛型类型变量

在泛型函数内部,类型参数可以被当作类型使用。

function loggingIdentity<T>(arg: T[]): T[] {
  console.log(`数组长度: ${arg.length}`)
  return arg
}

const result = loggingIdentity([1, 2, 3])
console.log(result)

T[] 表示 T 类型的数组,我们可以访问数组的 length 属性。类型参数 T 在函数内部可以用于类型注解。

泛型接口

接口可以使用泛型,定义可复用的类型结构。

interface Container<T> {
  value: T
  getValue(): T
  setValue(value: T): void
}

class Box<T> implements Container<T> {
  value: T

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

  getValue(): T {
    return this.value
  }

  setValue(value: T): void {
    this.value = value
  }
}

const stringBox = new Box("hello")
const numberBox = new Box(123)

console.log(stringBox.getValue())
console.log(numberBox.getValue())

Container 接口使用泛型 TBox 类实现这个接口时指定了具体的类型。

泛型类

类可以使用泛型,创建可复用的类定义。

class Stack<T> {
  private items: T[] = []

  push(item: T): void {
    this.items.push(item)
  }

  pop(): T | undefined {
    return this.items.pop()
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1]
  }

  isEmpty(): boolean {
    return this.items.length === 0
  }
}

const numberStack = new Stack<number>()
numberStack.push(1)
numberStack.push(2)
console.log(numberStack.pop())

const stringStack = new Stack<string>()
stringStack.push("a")
stringStack.push("b")
console.log(stringStack.pop())

Stack 类使用泛型 T,可以创建存储任意类型数据的栈。实例化时指定具体类型。

泛型约束

默认情况下,泛型可以接受任意类型。有时我们需要限制泛型的范围,这时可以使用泛型约束。

interface Lengthwise {
  length: number
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(`长度: ${arg.length}`)
  return arg
}

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

T extends Lengthwise 表示 T 必须有 length 属性。这让我们可以在函数内部安全地访问 length

泛型默认类型

可以为泛型参数提供默认类型,简化使用。

interface Response<T = any> {
  code: number
  message: string
  data: T
}

const response1: Response = {
  code: 200,
  message: "success",
  data: { name: "张三" }
}

const response2: Response<string> = {
  code: 200,
  message: "success",
  data: "hello"
}

Response 的泛型 T 默认是 any,使用时可以省略类型参数。

实际应用

泛型在实际开发中有很多应用场景,比如定义通用的 API 响应类型:

interface ApiResponse<T> {
  code: number
  message: string
  data: T
}

interface User {
  id: number
  name: string
}

interface Product {
  id: number
  name: string
  price: number
}

async function fetchUser(id: number): Promise<ApiResponse<User>> {
  return {
    code: 200,
    message: "success",
    data: { id, name: "张三" }
  }
}

async function fetchProducts(): Promise<ApiResponse<Product[]>> {
  return {
    code: 200,
    message: "success",
    data: [
      { id: 1, name: "商品1", price: 100 },
      { id: 2, name: "商品2", price: 200 }
    ]
  }
}

async function main() {
  const userResponse = await fetchUser(1)
  console.log(userResponse.data.name)

  const productsResponse = await fetchProducts()
  console.log(productsResponse.data.length)
}

main()

泛型让 API 响应类型可以复用,同时保持类型安全。

小结

泛型是 TypeScript 的核心特性之一,它让代码更加灵活和可复用。通过泛型,我们可以编写处理多种类型的代码,同时保持完整的类型检查。泛型在函数、接口、类中都有广泛应用,是构建类型安全可复用代码的重要工具。