可选参数

在 TypeScript 中,函数参数默认是必须的,调用时必须传入对应的值。但有些情况下,某些参数可以省略,这时可以使用可选参数语法。

可选参数语法

在参数名后添加 ? 符号,表示该参数是可选的。

function greet(name: string, greeting?: string): string {
  if (greeting) {
    return `${greeting}, ${name}!`
  }
  return `你好, ${name}!`
}

console.log(greet("张三"))
console.log(greet("李四", "早上好"))

greeting 是可选参数,调用时可以省略。在函数内部需要检查该参数是否存在。

可选参数的位置

可选参数必须放在必需参数之后。

function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return `${firstName} ${lastName}`
  }
  return firstName
}

console.log(buildName("张"))
console.log(buildName("张", "三"))

如果可选参数在必需参数前面,编译时会报错。这是 TypeScript 的语法规则。

可选参数与 undefined

可选参数在未传入时,值是 undefined。可以显式传入 undefined

function printInfo(name: string, age?: number): void {
  console.log(`姓名: ${name}`)
  console.log(`年龄: ${age ?? "未知"}`)
}

printInfo("张三")
printInfo("李四", 25)
printInfo("王五", undefined)

无论省略参数还是传入 undefined,函数内的行为是一致的。

可选参数的类型

可选参数的类型实际上是 T | undefined,其中 T 是声明的类型。

function process(value?: string): void {
  console.log(typeof value)
  if (value !== undefined) {
    console.log(value.toUpperCase())
  }
}

process()
process("hello")

在函数内部,需要先检查参数是否为 undefined,才能使用该参数的方法。

可选回调参数

回调函数的参数也可以是可选的,这在定义事件处理器时很有用。

type EventHandler = (event?: Event) => void

function addListener(handler: EventHandler): void {
  handler()
  handler({ type: "click" } as Event)
}

addListener((event) => {
  if (event) {
    console.log(`事件类型: ${event.type}`)
  } else {
    console.log("无事件数据")
  }
})

回调函数可以选择是否使用传入的事件对象。

可选参数与默认参数

可选参数和默认参数可以实现类似的效果,但有一些区别。

function greet1(name: string, greeting?: string): string {
  return `${greeting || "你好"}, ${name}!`
}

function greet2(name: string, greeting: string = "你好"): string {
  return `${greeting}, ${name}!`
}

console.log(greet1("张三"))
console.log(greet2("李四"))
console.log(greet1("王五", undefined))
console.log(greet2("赵六", undefined))

默认参数在传入 undefined 时会使用默认值,可选参数则保持 undefined。默认参数不需要放在参数列表的最后。

可选属性与可选参数

接口的可选属性和函数的可选参数使用相同的语法。

interface UserOptions {
  name: string
  age?: number
  email?: string
}

function createUser(options: UserOptions): void {
  console.log(`创建用户: ${options.name}`)
  console.log(`年龄: ${options.age ?? "未设置"}`)
  console.log(`邮箱: ${options.email ?? "未设置"}`)
}

createUser({ name: "张三" })
createUser({ name: "李四", age: 25, email: "lisi@example.com" })

接口的可选属性和函数的可选参数概念一致,都表示可以省略。

多个可选参数

函数可以有多个可选参数,按顺序排列。

function formatAddress(
  province: string,
  city: string,
  district?: string,
  street?: string
): string {
  let address = `${province} ${city}`
  if (district) {
    address += ` ${district}`
  }
  if (street) {
    address += ` ${street}`
  }
  return address
}

console.log(formatAddress("北京市", "北京市"))
console.log(formatAddress("北京市", "北京市", "朝阳区"))
console.log(formatAddress("北京市", "北京市", "朝阳区", "建国路"))

多个可选参数时,不能跳过前面的可选参数只传后面的。如果需要跳过,可以传入 undefined

可选参数的重载

函数重载可以更精确地描述可选参数的不同调用方式。

function createElement(tag: string): HTMLElement
function createElement(tag: string, className: string): HTMLElement
function createElement(tag: string, className?: string): HTMLElement {
  const element = document.createElement(tag)
  if (className) {
    element.className = className
  }
  return element
}

const div1 = createElement("div")
const div2 = createElement("div", "container")

重载签名让函数的调用方式更加明确,IDE 也能提供更好的提示。

实际应用

可选参数在实际开发中常用于配置对象和选项参数。

interface FetchOptions {
  method?: "GET" | "POST" | "PUT" | "DELETE"
  headers?: Record<string, string>
  body?: any
}

function fetch(url: string, options?: FetchOptions): Promise<any> {
  const method = options?.method || "GET"
  const headers = options?.headers || {}
  const body = options?.body

  console.log(`请求 ${url}`)
  console.log(`方法: ${method}`)
  console.log(`头部:`, headers)
  if (body) {
    console.log(`请求体:`, body)
  }

  return Promise.resolve({ data: "响应数据" })
}

fetch("https://api.example.com/data")
fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: { name: "张三" }
})

FetchOptions 的所有属性都是可选的,调用时可以只传需要的选项。

小结

可选参数使用 ? 语法,表示调用时可以省略该参数。可选参数必须放在必需参数之后,其类型实际上是 T | undefined。在函数内部需要检查可选参数是否存在,才能安全使用。可选参数让函数接口更加灵活,适合定义有默认行为的函数。