函数重载

函数重载允许为同一个函数提供多个类型签名,根据传入参数的类型和数量,TypeScript 会选择合适的签名进行类型检查。这让函数的调用方式更加明确,IDE 也能提供更好的提示。

基本语法

函数重载由重载签名和实现签名组成。重载签名描述函数的不同调用方式,实现签名包含实际的逻辑代码。

function format(input: string): string
function format(input: number): string
function format(input: string | number): string {
  if (typeof input === "string") {
    return `字符串: ${input}`
  }
  return `数字: ${input}`
}

console.log(format("hello"))
console.log(format(123))

前两行是重载签名,第三行是实现签名。调用时只能使用重载签名中定义的方式。

重载签名与实现签名

实现签名必须兼容所有重载签名,参数类型使用联合类型,返回值类型也要兼容。

function makeDate(timestamp: number): Date
function makeDate(year: number, month: number, day: number): Date
function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date {
  if (month !== undefined && day !== undefined) {
    return new Date(yearOrTimestamp, month - 1, day)
  }
  return new Date(yearOrTimestamp)
}

const d1 = makeDate(1234567890000)
const d2 = makeDate(2024, 1, 1)

实现签名的参数使用可选参数,以兼容两种调用方式。

返回值类型推断

不同的重载签名可以有不同的返回值类型。

function parse(input: string): string
function parse(input: number): number
function parse(input: string | number): string | number {
  return input
}

const str = parse("hello")
const num = parse(123)

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

TypeScript 会根据传入参数的类型推断返回值的类型,这是重载的一个重要用途。

参数数量不同的重载

重载可以描述参数数量不同的调用方式。

function createElement(tag: "a"): HTMLAnchorElement
function createElement(tag: "canvas"): HTMLCanvasElement
function createElement(tag: "table"): HTMLTableElement
function createElement(tag: string): HTMLElement
function createElement(tag: string): HTMLElement {
  return document.createElement(tag)
}

const anchor = createElement("a")
const canvas = createElement("canvas")
const div = createElement("div")

不同的标签返回不同的元素类型,让类型系统更加精确。

重载与联合类型

有些情况下,联合类型比重载更简单。

function format1(input: string | number): string {
  if (typeof input === "string") {
    return `字符串: ${input}`
  }
  return `数字: ${input}`
}

function format2(input: string): string
function format2(input: number): string
function format2(input: string | number): string {
  if (typeof input === "string") {
    return `字符串: ${input}`
  }
  return `数字: ${input}`
}

两种方式都能实现类似的效果。重载的优势在于可以更精确地描述不同参数类型对应的返回值类型。

方法重载

类中的方法也可以重载。

class Calculator {
  add(a: number, b: number): number
  add(a: string, b: string): string
  add(a: number | string, b: number | string): number | string {
    if (typeof a === "number" && typeof b === "number") {
      return a + b
    }
    return String(a) + String(b)
  }
}

const calc = new Calculator()
console.log(calc.add(1, 2))
console.log(calc.add("hello", "world"))

方法重载让类的接口更加清晰,调用者可以根据参数类型知道返回值类型。

重载的顺序

重载签名按照定义的顺序匹配,更具体的签名应该放在前面。

function process(value: string): string
function process(value: any): any
function process(value: any): any {
  return value
}

const result1 = process("hello")
const result2 = process(123)

如果先定义 any 类型的签名,后面的签名永远不会被匹配。所以要把更具体的签名放在前面。

重载与可选参数

重载签名中不使用可选参数语法,而是为每种情况定义单独的签名。

function fetch(url: string): Promise<any>
function fetch(url: string, options: RequestInit): Promise<any>
function fetch(url: string, options?: RequestInit): Promise<any> {
  console.log(`请求 ${url}`)
  if (options) {
    console.log(`选项:`, options)
  }
  return Promise.resolve({ data: "响应数据" })
}

const result1 = fetch("https://api.example.com/data")
const result2 = fetch("https://api.example.com/data", { method: "POST" })

实现签名使用可选参数,重载签名分别描述有无 options 参数的情况。

实际应用

函数重载在实际开发中常用于工具函数和库函数。

interface QueryResult {
  id: number
  name: string
}

function query(sql: string): QueryResult[]
function query(sql: string, params: any[]): QueryResult[]
function query(sql: string, params?: any[]): QueryResult[] {
  console.log(`执行 SQL: ${sql}`)
  if (params) {
    console.log(`参数:`, params)
  }
  return [{ id: 1, name: "张三" }]
}

const result1 = query("SELECT * FROM users")
const result2 = query("SELECT * FROM users WHERE id = ?", [1])

console.log(result1)
console.log(result2)

query 函数有两种调用方式,重载签名清晰地描述了这两种方式。

小结

函数重载为同一个函数提供多个类型签名,让函数的调用方式更加明确。重载签名描述不同的调用方式,实现签名包含实际逻辑。重载的优势在于可以精确描述参数类型与返回值类型的对应关系,提供更好的类型检查和 IDE 提示。合理使用重载可以让函数接口更加清晰易用。