TypeScript 介面 (Interface)
介面 (Interface) 是 TypeScript 中用來定義物件結構的一種方式。它可以描述物件應該有哪些屬性和方法,是建立型別契約的強大工具。
基本介面定義
interface User {
name: string;
age: number;
email: string;
}
// 使用介面
let user: User = {
name: '小明',
age: 25,
email: 'ming@example.com',
};
可選屬性
使用 ? 標記可選屬性:
interface User {
name: string;
age: number;
email?: string; // 可選
phone?: string; // 可選
}
let user1: User = { name: '小明', age: 25 };
let user2: User = { name: '小華', age: 30, email: 'hua@example.com' };
唯讀屬性
使用 readonly 標記唯讀屬性:
interface Point {
readonly x: number;
readonly y: number;
}
let point: Point = { x: 10, y: 20 };
// point.x = 30; // 錯誤:無法指派給 'x',因為它是唯讀屬性
ReadonlyArray
TypeScript 也提供 ReadonlyArray<T> 型別:
let numbers: ReadonlyArray<number> = [1, 2, 3, 4];
// numbers.push(5); // 錯誤
// numbers[0] = 10; // 錯誤
方法定義
介面可以定義方法:
interface User {
name: string;
age: number;
greet(): string;
greetWith(greeting: string): string;
}
let user: User = {
name: '小明',
age: 25,
greet() {
return `Hello, I'm ${this.name}`;
},
greetWith(greeting: string) {
return `${greeting}, I'm ${this.name}`;
},
};
console.log(user.greet()); // Hello, I'm 小明
console.log(user.greetWith('早安')); // 早安, I'm 小明
索引簽名
定義可以用索引存取的物件:
interface StringDictionary {
[key: string]: string;
}
let dict: StringDictionary = {
apple: '蘋果',
banana: '香蕉',
};
dict['orange'] = '橘子';
console.log(dict['apple']); // 蘋果
結合固定屬性
interface Config {
name: string;
[key: string]: string; // 其他屬性都是 string
}
let config: Config = {
name: 'app',
version: '1.0.0',
author: '小明',
};
函式型別介面
介面可以描述函式的型別:
interface MathOperation {
(a: number, b: number): number;
}
let add: MathOperation = (a, b) => a + b;
let subtract: MathOperation = (a, b) => a - b;
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
可呼叫物件
interface Counter {
(): number; // 可呼叫
count: number; // 屬性
reset(): void; // 方法
}
function createCounter(): Counter {
let count = 0;
const counter = function (): number {
return ++count;
} as Counter;
counter.count = count;
counter.reset = function () {
count = 0;
};
return counter;
}
let counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
counter.reset();
console.log(counter()); // 1
介面繼承 (Extending Interfaces)
使用 extends 繼承介面:
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
let dog: Dog = {
name: '小黑',
breed: '柴犬',
bark() {
console.log('汪汪!');
},
};
多重繼承
interface Named {
name: string;
}
interface Aged {
age: number;
}
interface Person extends Named, Aged {
email: string;
}
let person: Person = {
name: '小明',
age: 25,
email: 'ming@example.com',
};
介面合併 (Declaration Merging)
同名的介面會自動合併:
interface User {
name: string;
}
interface User {
age: number;
}
// User 現在有 name 和 age 兩個屬性
let user: User = {
name: '小明',
age: 25,
};
這個特性常用於擴展第三方函式庫的型別定義。
類別實作介面
類別可以使用 implements 實作介面:
interface Printable {
print(): void;
}
interface Loggable {
log(message: string): void;
}
class Document implements Printable, Loggable {
constructor(public content: string) {}
print() {
console.log(`Printing: ${this.content}`);
}
log(message: string) {
console.log(`Log: ${message}`);
}
}
let doc = new Document('Hello World');
doc.print(); // Printing: Hello World
doc.log('Created'); // Log: Created
泛型介面 (Generic Interfaces)
介面可以使用泛型:
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}
class Box<T> implements Container<T> {
constructor(public value: T) {}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
let numberBox = new Box<number>(42);
let stringBox = new Box<string>('hello');
泛型函式介面
interface GenericIdentityFn<T> {
(arg: T): T;
}
let identity: GenericIdentityFn<number> = (arg) => arg;
console.log(identity(42)); // 42
Interface vs Type Alias
介面和型別別名有很多相似之處,但也有一些差異:
| 特性 | Interface | Type Alias |
|---|---|---|
| 宣告合併 | ✓ 支援 | ✗ 不支援 |
| extends/implements | ✓ 支援 | 需用 & 交集 |
| 聯合型別 | ✗ 不支援 | ✓ 支援 |
| 原始型別別名 | ✗ 不支援 | ✓ 支援 |
| 元組 | 可用但較繁瑣 | ✓ 更簡潔 |
// 只有 type 能做到的
type StringOrNumber = string | number;
type Tuple = [string, number];
// 只有 interface 能做到的
interface User {
name: string;
}
interface User {
age: number;
} // 宣告合併
選擇建議
- 定義物件結構時,優先使用
interface - 需要聯合型別、交集型別或元組時,使用
type - 需要宣告合併時,使用
interface
實用範例
React 元件 Props
interface ButtonProps {
text: string;
onClick: () => void;
disabled?: boolean;
variant?: 'primary' | 'secondary' | 'danger';
}
// 使用 Props
function Button({ text, onClick, disabled = false, variant = 'primary' }: ButtonProps) {
// ...
}
API 回應型別
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
timestamp: number;
}
interface User {
id: number;
name: string;
email: string;
}
interface PaginatedResponse<T> extends ApiResponse<T[]> {
page: number;
totalPages: number;
totalItems: number;
}
// 使用
let response: PaginatedResponse<User>;
事件處理器
interface EventHandler<E extends Event = Event> {
(event: E): void;
}
interface ClickHandler extends EventHandler<MouseEvent> {}
let handleClick: ClickHandler = (event) => {
console.log(event.clientX, event.clientY);
};