跳至主要內容
Skip to content

TypeScript 函式類型與重載

函式是程式的基本構建塊。本篇將帶你掌握 TypeScript 中函式類型的所有面向。


一、 基本函式類型

1.1 參數與回傳值

typescript
// 具名函式
function add(a: number, b: number): number {
  return a + b
}

// 箭頭函式
const multiply = (a: number, b: number): number => {
  return a * b
}

// 簡寫(單一表達式)
const divide = (a: number, b: number): number => a / b

1.2 類型推論

typescript
// 回傳值可以推論
function add(a: number, b: number) {
  return a + b // 推論回傳 number
}

// 參數必須標註(無法推論)
// function add(a, b) {}  // 錯誤!

1.3 void 回傳

typescript
// 沒有回傳值
function log(message: string): void {
  console.log(message)
}

// 箭頭函式
const greet = (name: string): void => {
  console.log(`Hello, ${name}!`)
}

二、 可選參數與預設值

2.1 可選參數 (?)

typescript
function greet(name: string, greeting?: string): string {
  return `${greeting ?? "Hello"}, ${name}!`
}

greet('John') // 'Hello, John!'
greet('John', 'Hi') // 'Hi, John!'

WARNING

可選參數必須放在必填參數之後!

typescript
// 正確
function fn(a: string, b?: number): void {}

// 錯誤
// function fn(a?: string, b: number): void {}

2.2 預設參數值

typescript
function greet(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}!`
}

greet('John') // 'Hello, John!'
greet('John', 'Hi') // 'Hi, John!'

// 預設值參數可以不在最後
function createUser(name: string, age: number = 18, email: string) {
  return { name, age, email }
}

createUser('John', undefined, 'john@example.com') // age = 18

三、 剩餘參數(Rest Parameters)

3.1 收集所有參數

typescript
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, num) => acc + num, 0)
}

sum(1, 2, 3) // 6
sum(1, 2, 3, 4, 5) // 15

3.2 混合使用

typescript
function log(prefix: string, ...messages: string[]): void {
  messages.forEach((msg) => console.log(`${prefix}: ${msg}`));
}

log('INFO', 'Starting app', 'Loading config')
// INFO: Starting app
// INFO: Loading config

3.3 元組型態的剩餘參數

typescript
function formatDate(...args: [number, number, number]): string {
  const [year, month, day] = args
  return `${year}-${month}-${day}`
}

formatDate(2025, 1, 13) // '2025-1-13'

四、 函式型態表達式

4.1 定義函式型態

typescript
// 函式型態表達式
type MathOperation = (a: number, b: number) => number

const add: MathOperation = (a, b) => a + b
const subtract: MathOperation = (a, b) => a - b
const multiply: MathOperation = (a, b) => a * b

4.2 作為參數

typescript
type Callback = (result: number) => void

function calculate(
  a: number,
  b: number,
  operation: (x: number, y: number) => number,
  callback: Callback
): void {
  const result = operation(a, b)
  callback(result)
}

calculate(
  10,
  5,
  (a, b) => a + b,
  (result) => {
    console.log(`Result: ${result}`) // Result: 15
  }
)

4.3 作為回傳值

typescript
function createAdder(base: number): (n: number) => number {
  return (n: number) => base + n
}

const add10 = createAdder(10)
console.log(add10(5)) // 15
console.log(add10(20)) // 30

五、 函式重載(Overloads)

5.1 為什麼需要重載?

typescript
// 這樣寫無法精確表達
function process(input: string | number): string | number {
  if (typeof input === 'string') {
    return input.toUpperCase()
  }
  return input * 2
}

const result = process('hello') // string | number,不精確!

5.2 使用重載

typescript
// 重載簽名
function process(input: string): string
function process(input: number): number

// 實作簽名
function process(input: string | number): string | number {
  if (typeof input === 'string') {
    return input.toUpperCase()
  }
  return input * 2
}

const str = process('hello') // string 
const num = process(10) // number

5.3 多種重載

typescript
// 根據參數數量重載
function createElement(tag: string): HTMLElement
function createElement(tag: string, content: string): HTMLElement
function createElement(
  tag: string,
  attrs: object,
  content: string
): HTMLElement

function createElement(
  tag: string,
  attrsOrContent?: string | object,
  content?: string
): HTMLElement {
  const el = document.createElement(tag)

  if (typeof attrsOrContent === 'string') {
    el.textContent = attrsOrContent
  } else if (attrsOrContent) {
    Object.assign(el, attrsOrContent)
    if (content) el.textContent = content
  }

  return el
}

createElement('div')
createElement('p', 'Hello')
createElement('a', { href: '#' }, 'Link')

5.4 重載注意事項

typescript
// 1. 重載簽名必須在實作簽名之前
// 2. 實作簽名對外不可見
// 3. 實作簽名必須兼容所有重載簽名

// 不能這樣呼叫(實作簽名不可見)
// process('hello', 123)  // 錯誤

六、 this 類型

6.1 明確 this 類型

typescript
interface User {
  name: string
  greet(this: User): void
}

const user: User = {
  name: 'John',
  greet() {
    console.log(`Hello, ${this.name}!`)
  },
}

user.greet() // OK

const greet = user.greet
// greet()  // 錯誤!this 不是 User

6.2 箭頭函式與 this

typescript
class Counter {
  count = 0

  // 箭頭函式綁定 this
  increment = (): void => {
    this.count++
  }
}

const counter = new Counter()
const inc = counter.increment
inc() // OK,this 被正確綁定

七、 泛型函式(預告)

typescript
// 泛型讓函式更靈活(詳見後續章節)
function identity<T>(value: T): T {
  return value
}

const str = identity('hello') // string
const num = identity(42) // number

八、 實用範例

8.1 事件處理器

typescript
type EventHandler<E extends Event> = (event: E) => void

function addClickListener(
  element: HTMLElement,
  handler: EventHandler<MouseEvent>
): void {
  element.addEventListener('click', handler)
}

addClickListener(document.body, (event) => {
  console.log(event.clientX, event.clientY)
})

8.2 非同步函式

typescript
async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`)
  return response.json()
}

interface User {
  id: number
  name: string
}

// 使用
const user = await fetchUser(1) // User

8.3 高階函式

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

  return (...args) => {
    clearTimeout(timeoutId)
    timeoutId = setTimeout(() => fn(...args), delay)
  }
}

const debouncedLog = debounce((msg: string) => console.log(msg), 300)
debouncedLog('Hello')

總結

語法說明範例
(a: T) => R函式類型(n: number) => string
param?可選參數fn(a: string, b?: number)
param = value預設值fn(a: string = 'hello')
...rest剩餘參數fn(...args: number[])
重載多種簽名見上方範例

> **設計原則**:

  • 參數越少越好
  • 使用物件參數處理多參數
  • 善用預設值減少調用複雜度

進階挑戰

  1. 實作一個 pipe 函式,將多個單參數函式組合:
typescript
// pipe(fn1, fn2, fn3)(value)
// 等同於 fn3(fn2(fn1(value)))

const addOne = (n: number) => n + 1
const double = (n: number) => n * 2
const toString = (n: number) => String(n)

// 實作 pipe 函式
// const result = pipe(addOne, double, toString)(5)
// 結果應該是 '12'
  1. 為以下函式加上重載,使其回傳精確類型:
typescript
function parse(input: string): object
function parse(input: object): string
// 實作...

延伸閱讀與資源