本文是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...")
};
接口繼承的實際價值
- 代碼複用:避免重複定義相同的屬性
- 清晰的類型關係:明確表達類型之間的"是一種"關係
- 可維護性:修改基礎接口會自動影響所有擴展接口
五、函數類型接口
定義函數
接口不僅可以描述對象,還可以描述函數類型。這對於定義回調函數、事件處理器等非常有用。
函數類型接口語法:
// 定義函數接口
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. 單一職責原則
每個接口應該只關注一個特定的功能領域:
// 不好的設計:接口承擔太多職責
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) {
// 組件實現
}
十、總結
核心概念回顧
- 對象字面量類型:直接描述對象形狀的基礎方式
- 接口:正式的對象類型契約,支持繼承和實現
- 可選屬性:處理可能缺失的屬性
- 只讀屬性:確保對象創建後的不可變性
- 接口繼承:構建清晰的類型層次結構
- 函數類型接口:定義函數契約
- 可索引接口:處理動態屬性結構
實際開發價值
- 提高代碼可靠性:編譯時檢查對象結構
- 增強代碼可讀性:接口作為文檔,明確數據契約
- 促進代碼複用:通過繼承和組合重用類型定義
- 支持團隊協作:明確的接口定義減少溝通成本