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

介面和型別別名有很多相似之處,但也有一些差異:

特性InterfaceType 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);
};