泛型函数

泛型函数是使用泛型的函数,它可以处理多种类型的参数,同时保持类型安全。泛型函数是代码复用的重要手段。

基本语法

在函数名后添加 <T> 声明类型参数,然后在参数和返回值中使用这个类型。

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 类型的参数,返回相同类型的值。调用时可以显式指定类型或让 TypeScript 推断。

泛型函数类型

泛型函数的类型定义需要包含类型参数。

type Identity<T> = (arg: T) => T

const identity: Identity<string> = (arg) => arg
console.log(identity("hello"))

function createIdentity<T>(): Identity<T> {
  return (arg: T) => arg
}

const numberIdentity = createIdentity<number>()
console.log(numberIdentity(123))

Identity<T> 是一个泛型函数类型,描述了接受 T 返回 T 的函数。

数组操作函数

泛型函数常用于数组操作,可以处理任意类型的数组。

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 numbers = [1, 2, 3, 4, 5]
console.log(first(numbers))
console.log(last(numbers))
console.log(filter(numbers, n => n > 2))

const strings = ["a", "b", "c"]
console.log(first(strings))
console.log(filter(strings, s => s !== "b"))

这些函数可以处理数字数组、字符串数组或任何其他类型的数组。

对象操作函数

泛型函数也可以用于对象操作。

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value
}

const user = {
  id: 1,
  name: "张三",
  email: "zhangsan@example.com"
}

console.log(getProperty(user, "name"))
setProperty(user, "name", "李四")
console.log(user.name)

keyof T 获取类型 T 的所有键的联合类型,K extends keyof T 确保 key 参数是有效的键。

高阶函数

泛型函数可以创建高阶函数,返回新的函数。

function createValidator<T>(rules: Partial<Record<keyof T, (value: any) => boolean>>) {
  return (obj: T): boolean => {
    for (const key in rules) {
      const rule = rules[key]
      if (rule && !rule(obj[key])) {
        return false
      }
    }
    return true
  }
}

interface User {
  name: string
  age: number
}

const validateUser = createValidator<User>({
  name: (value) => value.length > 0,
  age: (value) => value >= 0 && value <= 150
})

console.log(validateUser({ name: "张三", age: 25 }))
console.log(validateUser({ name: "", age: 25 }))
console.log(validateUser({ name: "李四", age: 200 }))

createValidator 返回一个验证函数,可以根据规则验证对象。

异步函数

泛型函数在异步编程中也很常用。

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url)
  return response.json()
}

interface User {
  id: number
  name: string
}

async function main() {
  const user = await fetchData<User>("https://api.example.com/users/1")
  console.log(user.name)
}

fetchData 函数返回一个 Promise<T>,调用时指定期望的响应类型。

函数重载与泛型

泛型函数可以与函数重载结合使用。

function convert(value: string): number
function convert(value: number): string
function convert<T extends string | number>(value: T): T extends string ? number : string
function convert(value: string | number): string | number {
  if (typeof value === "string") {
    return parseInt(value, 10)
  }
  return String(value)
}

const num = convert("123")
const str = convert(456)

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

重载签名提供了更精确的类型信息,条件类型让返回值类型与参数类型关联。

实际应用

泛型函数在实际开发中有很多应用场景。

function debounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number
): (...args: Parameters<T>) => void {
  let timer: ReturnType<typeof setTimeout>
  return (...args: Parameters<T>) => {
    clearTimeout(timer)
    timer = setTimeout(() => fn(...args), delay)
  }
}

function throttle<T extends (...args: any[]) => any>(
  fn: T,
  interval: number
): (...args: Parameters<T>) => void {
  let lastTime = 0
  return (...args: Parameters<T>) => {
    const now = Date.now()
    if (now - lastTime >= interval) {
      lastTime = now
      fn(...args)
    }
  }
}

const logMessage = (msg: string) => console.log(msg)

const debouncedLog = debounce(logMessage, 300)
const throttledLog = throttle(logMessage, 1000)

debouncedLog("消息1")
debouncedLog("消息2")
debouncedLog("消息3")

throttledLog("节流消息1")
throttledLog("节流消息2")

debouncethrottle 是通用的工具函数,可以包装任意函数。Parameters<T> 获取函数类型的参数类型。

小结

泛型函数是 TypeScript 中代码复用的重要工具。通过类型参数,函数可以处理多种类型,同时保持类型安全。泛型函数可以用于数组操作、对象操作、高阶函数和异步编程。合理使用泛型函数可以减少代码重复,提高代码的可维护性。