本文是TypeScript系列第五篇,將深入探討TypeScript中函數類型的各種用法。函數是JavaScript的核心概念,TypeScript為函數提供了強大的類型支持,幫助您編寫更安全、更易維護的函數代碼。

一、函數聲明的類型定義

函數聲明的基本類型語法

        在TypeScript中,我們可以為函數的參數和返回值添加類型註解,這讓函數的預期行為更加明確。

基本語法:

// 為參數和返回值添加類型
function greet(name: string): string {
    return `Hello, ${name}`;
}

// 沒有返回值的函數使用void
function logMessage(message: string): void {
    console.log(message);
}

為什麼需要函數類型註解?

        函數類型註解提供了三個重要價值:

  1. 明確函數契約:調用者清楚知道需要傳遞什麼參數,會得到什麼返回值
  2. 早期錯誤檢測:在調用函數時就能發現參數類型不匹配的問題
  3. 更好的開發體驗:IDE能夠提供準確的參數提示和文檔

實際對比:

// JavaScript - 調用時可能出錯
function calculateTotal(price, quantity) {
    return price * quantity;
}
// 可能意外傳遞字符串
calculateTotal("10", 2); // 返回"102"而不是20

// TypeScript - 編譯時發現錯誤
function calculateTotal(price: number, quantity: number): number {
    return price * quantity;
}
calculateTotal("10", 2); // 編譯錯誤:類型不匹配

二、函數表達式的類型註解

函數表達式的類型寫法

        函數表達式在TypeScript中有兩種常見的類型註解方式:

方式1:內聯類型註解

const greet: (name: string) => string = function(name: string): string {
    return `Hello, ${name}`;
};

方式2:使用類型別名(推薦)

// 先定義函數類型
type GreetFunction = (name: string) => string;

// 然後使用這個類型
const greet: GreetFunction = function(name) {
    return `Hello, ${name}`;
};

類型推斷在函數表達式中的應用

        TypeScript能夠根據上下文推斷函數表達式的類型,這讓代碼更加簡潔:

// TypeScript能夠推斷出add函數的類型
const add = function(a: number, b: number) {
    return a + b; // 推斷返回值為number
};

// 等價於顯式註解:
const add: (a: number, b: number) => number = function(a, b) {
    return a + b;
};

三、可選參數與默認參數

可選參數的處理

在實際開發中,我們經常需要處理可選參數。TypeScript使用問號(?)標識可選參數。

可選參數語法:

function createUser(name: string, age?: number): User {
    return {
        name,
        age: age ?? 0 // 處理可能的undefined
    };
}

// 調用方式
createUser("Alice"); // age是可選的
createUser("Bob", 25); // 也可以提供age

三、可選參數與默認參數

可選參數的處理

        在實際開發中,我們經常需要處理可選參數。TypeScript使用問號(?)標識可選參數。

可選參數語法:

function createUser(name: string, age?: number): User {
    return {
        name,
        age: age ?? 0 // 處理可能的undefined
    };
}

// 調用方式
createUser("Alice"); // age是可選的
createUser("Bob", 25); // 也可以提供age

重要注意事項:

  • 可選參數必須放在必需參數後面
  • 在函數內部,可選參數的類型是參數類型 | undefined

默認參數的類型推斷

        TypeScript能夠根據默認值自動推斷參數類型:

// 不需要顯式註解類型,TypeScript能推斷出age是number類型
function createUser(name: string, age = 0) {
    return { name, age };
}

// 等價於:
function createUser(name: string, age: number = 0) {
    return { name, age };
}

四、剩餘參數的類型定義

剩餘參數的基本用法

        剩餘參數允許函數接受任意數量的參數,在TypeScript中需要為其指定數組類型。

基本語法:

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

// 調用示例
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15

剩餘參數與普通參數結合

        剩餘參數可以與其他參數一起使用,但必須放在最後:

function introduce(greeting: string, ...names: string[]): string {
    return `${greeting} ${names.join('、')}!`;
}

// 調用示例
introduce("Hello", "Alice", "Bob", "Charlie");
// 返回: "Hello Alice、Bob、Charlie!"

五、箭頭函數的類型定義

箭頭函數的基本類型語法

        箭頭函數在TypeScript中的類型定義與普通函數類似:

// 內聯類型註解
const add = (a: number, b: number): number => a + b;

// 使用類型別名
type MathOperation = (x: number, y: number) => number;

const multiply: MathOperation = (a, b) => a * b;
const divide: MathOperation = (a, b) => a / b;

箭頭函數的上下文類型推斷

        箭頭函數在特定上下文中可以享受更強大的類型推斷:

// 在數組方法中,TypeScript能夠推斷參數類型
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2); 
// num自動推斷為number類型

// 在事件處理中,也能推斷事件類型
button.addEventListener('click', event => {
    // event被推斷為MouseEvent類型
    console.log(event.clientX, event.clientY);
});

六、函數重載的實用場景

什麼是函數重載?

        函數重載允許一個函數接受不同類型或數量的參數,並返回不同的類型。這在處理多種輸入情況時非常有用。

實際應用場景

場景1:處理不同參數類型

// 重載簽名
function processInput(input: string): string[];
function processInput(input: number): number[];

// 實現簽名
function processInput(input: string | number): string[] | number[] {
    if (typeof input === 'string') {
        return input.split('');
    } else {
        return [input, input * 2, input * 3];
    }
}

// 調用時獲得準確的類型提示
const chars = processInput("hello"); // 推斷為string[]
const numbers = processInput(5);     // 推斷為number[]

場景2:處理可選參數的不同組合

// 重載簽名
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'span'): HTMLSpanElement;
function createElement(tag: 'input'): HTMLInputElement;

// 實現簽名
function createElement(tag: 'div' | 'span' | 'input'): HTMLElement {
    // 實際實現
    return document.createElement(tag);
}

// 調用時獲得準確的返回類型
const div = createElement('div');    // HTMLDivElement
const input = createElement('input'); // HTMLInputElement

函數重載的使用建議

函數重載雖然強大,但也有其複雜性:

  1. 不要過度使用:只在真正需要處理多種明顯不同的情況時使用
  2. 保持重載簽名簡單:重載簽名應該易於理解
  3. 實現簽名要覆蓋所有情況:確保實現能夠處理所有重載情況

七、this參數的類型註解

理解this的類型問題

        在JavaScript中,this的指向是常見的困惑點。TypeScript允許我們為this參數添加類型註解,明確其預期類型。

基本語法:

interface User {
    name: string;
    age: number;
    greet(this: User): void;
}

const user: User = {
    name: "Alice",
    age: 25,
    greet() {
        console.log(`Hello, I'm ${this.name}`);
    }
};

實際應用場景

場景:事件處理函數中的this

class Button {
    text: string;
    
    constructor(text: string) {
        this.text = text;
    }
    
    // 明確指定this的類型
    handleClick(this: Button) {
        console.log(`Button "${this.text}" clicked`);
    }
}

const button = new Button("Submit");
button.handleClick(); //正確調用

const clickHandler = button.handleClick;
clickHandler(); // 錯誤:this上下文丟失

八、參數解構的類型寫法

對象參數解構的類型註解

        在處理配置對象等場景時,參數解構非常有用。TypeScript支持為解構參數添加類型註解。

基本語法:

// 為解構參數添加類型
function createUser({ name, age }: { name: string; age: number }): User {
    return { name, age };
}

// 使用類型別名提高可讀性
interface UserConfig {
    name: string;
    age: number;
    email?: string;
}

function createUser({ name, age, email = "" }: UserConfig): User {
    return { name, age, email };
}

帶默認值的參數解構

// 解構參數與默認值結合
function drawShape(
    { x = 0, y = 0, width = 100, height = 100 }: 
    { x?: number; y?: number; width?: number; height?: number }
) {
    console.log(`在 (${x}, ${y})繪製,大小為 ${width}x${height}`);
}

// 調用示例
drawShape({ x: 10, y: 20 }); // 使用部分參數
drawShape({}); // 使用所有默認值

九、實際開發中的最佳實踐

1. 合理使用類型推斷

        讓TypeScript推斷簡單的函數類型,減少樣板代碼

// 推薦:讓TypeScript推斷返回值類型
function add(a: number, b: number) {
    return a + b; // 推斷為number
}

// 不推薦:冗餘的類型註解
function add(a: number, b: number): number {
    return a + b;
}

2. 優先使用類型別名

        對於複雜的函數類型,使用類型別名提高可讀性:

//推薦:使用類型別名
type UserFactory = (name: string, age?: number) => User;

const createUser: UserFactory = (name, age = 0) => {
    return { name, age };
};

//不推薦:內聯複雜類型
const createUser: (name: string, age?: number) => User = (name, age = 0) => {
    return { name, age };
};

3. 處理異步函數的類型

        異步函數返回Promise,需要相應的類型註解:

// 異步函數的類型
async function fetchUser(id: number): Promise<User> {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
}

// 或者讓TypeScript推斷
async function fetchUser(id: number) {
    const response = await fetch(`/api/users/${id}`);
    return response.json() as Promise<User>;
}

十、總結

核心概念回顧

  1. 函數類型註解:明確參數和返回值的類型契約
  2. 可選參數和默認參數:處理靈活的函數調用方式
  3. 剩餘參數:處理可變數量參數的類型安全
  4. 函數重載:處理多種輸入輸出情況的類型設計
  5. this參數註解:明確函數執行上下文類型

實際開發要點

  • 為函數參數添加必要的類型註解
  • 讓TypeScript推斷返回值類型以減少冗餘
  • 使用類型別名提高複雜函數類型的可讀性
  • 謹慎使用函數重載,只在真正需要時使用

        掌握了函數類型後,下一篇我們將深入探討對象類型與接口。