本文是TypeScript系列第六篇,將深入講解TypeScript中對象類型和接口的使用。接口是TypeScript最核心的概念之一,它為我們提供了一種強大的方式來定義數據結構契約,讓代碼更加可靠和易於維護。

一、對象字面量類型:基礎對象結構定義

什麼是對象字面量類型?

        對象字面量類型允許我們直接描述對象的形狀,即對象應該有哪些屬性,以及這些屬性的類型是什麼。

基本語法:

// 直接定義對象類型
let user: { name: string; age: number } = {
    name: "Alice",
    age: 25
};

// 使用類型別名提高可讀性
type User = {
    name: string;
    age: number;
};

let user: User = {
    name: "Alice", 
    age: 25
};

對象類型的重要性

        對象類型檢查確保我們使用的對象符合預期的結構:

// TypeScript會報錯:缺少age屬性
let user: { name: string; age: number } = {
    name: "Alice"
};

// TypeScript會報錯:age屬性類型不正確
let user: { name: string; age: number } = {
    name: "Alice",
    age: "25" // 應該是number
};

二、接口(描述對象)

接口的基本概念

        接口是TypeScript中定義對象類型的首選方式。它提供了一種更正式、更可重用的方式來描述對象的形狀。

接口定義語法:

// 定義接口
interface User {
    name: string;
    age: number;
    email: string;
}

// 使用接口
const user: User = {
    name: "Alice",
    age: 25,
    email: "alice@example.com"
};

接口 vs 類型別名

接口和類型別名在很多情況下可以互換使用,但它們有不同的設計目的:

  • 接口主要用於定義對象的形狀,支持繼承聲明合併
  • 類型別名:可以為任何類型創建別名,包括聯合類型、元組

使用建議:

  • 定義對象類型時優先使用接口
  • 需要聯合類型或元組時使用類型別名

三、可選屬性與只讀屬性

可選屬性的實際應用

        在實際開發中,我們經常需要處理可能不存在的屬性。可選屬性使用問號(?)標記。

可選屬性語法:

interface User {
    name: string;
    age: number;
    phone?: string; // 可選屬性
    address?: string; // 可選屬性
}

// 所有用法都是有效的
const user1: User = { name: "Alice", age: 25 };
const user2: User = { name: "Bob", age: 30, phone: "123-4567" };
const user3: User = { name: "Charlie", age: 35, phone: "123-4567", address: "Beijing" };

可選屬性的價值:

  • 描述真實世界中的數據(某些信息可能缺失)
  • 提供靈活的API設計
  • 向後兼容的接口演化

只讀屬性的使用場景

        只讀屬性確保對象創建後某些屬性不能被修改,這在函數式編程和不可變數據中特別有用。

只讀屬性語法:

interface Point {
    readonly x: number;
    readonly y: number;
}

const point: Point = { x: 10, y: 20 };
point.x = 5; // 錯誤:無法分配到"x",因為它是隻讀屬性

只讀屬性的實際應用:

// 配置對象 - 創建後不應修改
interface AppConfig {
    readonly apiUrl: string;
    readonly timeout: number;
    readonly version: string;
}

const config: AppConfig = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    version: "1.0.0"
};

// config.apiUrl = "new-url"; //編譯錯誤

四、接口繼承:構建類型層次

接口繼承的基本概念

        接口繼承允許我們從一個或多個接口擴展,創建更具體的接口。這促進了代碼複用和清晰的類型層次結構。

單繼承示例:

// 基礎接口
interface Person {
    name: string;
    age: number;
}

// 擴展接口
interface Employee extends Person {
    employeeId: string;
    department: string;
}

// 使用擴展接口
const employee: Employee = {
    name: "Alice",
    age: 25,
    employeeId: "E123",
    department: "Engineering"
};

多繼承示例:

interface CanWalk {
    walk(): void;
}

interface CanTalk {
    talk(): void;
}

// 繼承多個接口
interface Human extends CanWalk, CanTalk {
    name: string;
}

const person: Human = {
    name: "Alice",
    walk: () => console.log("Walking..."),
    talk: () => console.log("Talking...")
};

接口繼承的實際價值

  1. 代碼複用:避免重複定義相同的屬性
  2. 清晰的類型關係:明確表達類型之間的"是一種"關係
  3. 可維護性:修改基礎接口會自動影響所有擴展接口

五、函數類型接口

定義函數

        接口不僅可以描述對象,還可以描述函數類型。這對於定義回調函數、事件處理器等非常有用。

函數類型接口語法:

// 定義函數接口
interface SearchFunction {
    (source: string, keyword: string): boolean;
}

// 實現函數接口
const search: SearchFunction = function(source, keyword) {
    return source.includes(keyword);
};

// 使用
const found = search("Hello TypeScript", "Type"); // true

函數類型接口的實際應用

// 事件處理器接口
interface ClickHandler {
    (event: MouseEvent): void;
}

// 排序函數接口
interface Comparator<T> {
    (a: T, b: T): number;
}

const numberComparator: Comparator<number> = (a, b) => a - b;
const stringComparator: Comparator<string> = (a, b) => a.localeCompare(b);

六、可索引類型接口

處理動態屬性

        可索引類型接口允許我們定義具有索引簽名的對象,這在處理字典、映射等動態數據結構時非常有用。

基本語法:

// 字符串索引接口
interface StringDictionary {
    [key: string]: string;
}

const colors: StringDictionary = {
    red: "#FF0000",
    green: "#00FF00",
    blue: "#0000FF"
};

// 數字索引接口  
interface NumberArray {
    [index: number]: number;
}

const scores: NumberArray = [95, 87, 92];

索引簽名的實際應用

// 緩存接口
interface Cache {
    [key: string]: any;
    timeout: number; // 可以混合已知屬性
}

const cache: Cache = {
    timeout: 5000,
    userData: { name: "Alice" },
    settings: { theme: "dark" }
};

七、接口實現與類

類實現接口

        接口可以用於約束類的結構,確保類提供特定的屬性和方法。

類實現接口語法:

interface Animal {
    name: string;
    makeSound(): void;
}

// 類實現接口
class Dog implements Animal {
    name: string;
    
    constructor(name: string) {
        this.name = name;
    }
    
    makeSound(): void {
        console.log("Woof! Woof!");
    }
}

// 使用
const dog = new Dog("Buddy");
dog.makeSound(); // "Woof! Woof!"

接口實現的優勢

  1. 多態性:不同的類可以實現相同的接口
  2. 測試友好:可以使用模擬實現進行測試
  3. 依賴注入:基於接口而不是具體實現編程

八、實際開發中的接口設計原則

1. 單一職責原則

        每個接口應該只關注一個特定的功能領域:

// 不好的設計:接口承擔太多職責
interface UserService {
    getUser(id: number): User;
    saveUser(user: User): void;
    sendEmail(user: User, message: string): void;
    generateReport(): Report;
}

// 好的設計:分離關注點
interface UserRepository {
    getUser(id: number): User;
    saveUser(user: User): void;
}

interface EmailService {
    sendEmail(to: string, message: string): void;
}

interface ReportService {
    generateReport(): Report;
}

2. 面向接口編程

        基於接口而不是具體實現編寫代碼,提高靈活性和可測試性:

// 基於接口定義依賴
interface Logger {
    log(message: string): void;
}

class FileLogger implements Logger {
    log(message: string): void {
        // 寫入文件的實現
    }
}

class ConsoleLogger implements Logger {
    log(message: string): void {
        console.log(message);
    }
}

// 使用接口類型的參數
function processUser(user: User, logger: Logger) {
    logger.log(`Processing user: ${user.name}`);
    // 處理邏輯
}

3. 接口的漸進式定義

        TypeScript接口支持聲明合併,允許我們逐步定義接口:

// 第一次定義
interface User {
    name: string;
    age: number;
}

// 後續擴展(可能在另一個文件中)
interface User {
    email: string;
}

// 最終結果
const user: User = {
    name: "Alice",
    age: 25,
    email: "alice@example.com"
};

九、常見模式與最佳實踐

1. 使用接口描述API響應

// API響應接口
interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
    success: boolean;
}

// 用户數據接口
interface User {
    id: number;
    name: string;
    email: string;
}

// 使用組合接口
function handleUserResponse(response: ApiResponse<User>) {
    if (response.success) {
        console.log(`User: ${response.data.name}`);
    } else {
        console.error(`Error: ${response.message}`);
    }
}

2. 配置對象接口

// 應用配置接口
interface AppConfig {
    readonly apiUrl: string;
    readonly timeout: number;
    readonly features: {
        darkMode: boolean;
        notifications: boolean;
    };
}

// 使用默認值和可選屬性
interface DatabaseConfig {
    host: string;
    port: number;
    username?: string;
    password?: string;
    database: string;
}

3. 組件Props接口(React/Vue場景)

// React組件Props接口
interface ButtonProps {
    text: string;
    onClick: () => void;
    disabled?: boolean;
    variant?: 'primary' | 'secondary' | 'danger';
}

// 使用接口約束組件Props
function Button({ text, onClick, disabled = false, variant = 'primary' }: ButtonProps) {
    // 組件實現
}

十、總結

核心概念回顧

  1. 對象字面量類型:直接描述對象形狀的基礎方式
  2. 接口:正式的對象類型契約,支持繼承和實現
  3. 可選屬性:處理可能缺失的屬性
  4. 只讀屬性:確保對象創建後的不可變性
  5. 接口繼承:構建清晰的類型層次結構
  6. 函數類型接口:定義函數契約
  7. 可索引接口:處理動態屬性結構

實際開發價值

  • 提高代碼可靠性:編譯時檢查對象結構
  • 增強代碼可讀性:接口作為文檔,明確數據契約
  • 促進代碼複用:通過繼承和組合重用類型定義
  • 支持團隊協作:明確的接口定義減少溝通成本