[Medium] Interface vs Type Alias
1. What are Interface and Type Alias?
什麼是 Interface 和 Type Alias?
Interface(介面)
定義:用於定義物件的結構,描述物件應該有哪些屬性和方法。
interface User {
name: string;
age: number;
email?: string; // 可選屬性
}
const user: User = {
name: 'John',
age: 30,
};
Type Alias(型別別名)
定義:為型別建立一個別名,可以用於任何型別,不僅限於物件。
type User = {
name: string;
age: number;
email?: string;
};
const user: User = {
name: 'John',
age: 30,
};
2. Interface vs Type Alias: Key Differences
Interface 與 Type Alias 的主要差異
1. 擴展方式
Interface:使用 extends
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const dog: Dog = {
name: 'Buddy',
breed: 'Golden Retriever',
};
Type Alias:使 用交叉型別(Intersection)
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string;
};
const dog: Dog = {
name: 'Buddy',
breed: 'Golden Retriever',
};
2. 合併(Declaration Merging)
Interface:支援合併
interface User {
name: string;
}
interface User {
age: number;
}
// 自動合併為 { name: string; age: number; }
const user: User = {
name: 'John',
age: 30,
};
Type Alias:不支援合併
type User = {
name: string;
};
type User = { // ❌ 錯誤:Duplicate identifier 'User'
age: number;
};
3. 適用範圍
Interface:主要用於物件結構
interface User {
name: string;
age: number;
}
Type Alias:可用於任何型別
// 基本型別
type ID = string | number;
// 函式型別
type Greet = (name: string) => string;
// 聯合型別
type Status = 'active' | 'inactive' | 'pending';
// 元組
type Point = [number, number];
// 物件
type User = {
name: string;
age: number;
};
4. 計算屬性
Interface:不支援計算屬性
interface User {
[key: string]: any; // 索引簽名
}
Type Alias:支援更複雜的型別運算
type Keys = 'name' | 'age' | 'email';
type User = {
[K in Keys]: string; // 映射型別
};
3. When to Use Interface vs Type Alias?
何時使用 Interface?何時使用 Type Alias?
使用 Interface 的情況
-
定義物件結構(最常見)
interface User {
name: string;
age: number;
} -
需要宣告合併
// 擴展第三方套件的型別定義
interface Window {
myCustomProperty: string;
} -
定義類別(Class)的契約
interface Flyable {
fly(): void;
}
class Bird implements Flyable {
fly(): void {
console.log('Flying');
}
}
使用 Type Alias 的情況
-
定義聯合型別或交叉型別
type ID = string | number;
type Status = 'active' | 'inactive'; -
定義函式型別
type EventHandler = (event: Event) => void; -
定義元組
type Point = [number, number]; -
需要映射型別或條件型別
type Partial<T> = {
[P in keyof T]?: T[P];
};
4. Common Interview Questions
常見面試題目
題目 1:基本差異
請說明以下兩種定義方式的差異。
// 方式 1:使用 Interface
interface User {
name: string;
age: number;
}
// 方式 2:使用 Type Alias
type User = {
name: string;
age: number;
};
點擊查看答案
相同點:
- 兩者都可以用來定義物件結構
- 使用方式完全相同
- 都可以擴展和繼承
不同點:
-
宣告合併:
// Interface 支援
interface User {
name: string;
}
interface User {
age: number;
}
// 自動合併為 { name: string; age: number; }
// Type Alias 不支援
type User = { name: string; };
type User = { age: number; }; // ❌ 錯誤 -
適用範圍:
// Interface 主要用於物件
interface User { ... }
// Type Alias 可用於任何型別
type ID = string | number;
type Handler = (event: Event) => void;
type Point = [number, number];
建議:
- 定義物件結構時,兩者都可以使用
- 如果需要宣告合併,使用 Interface
- 如果需要定義非物件型別,使用 Type Alias
題目 2:擴展方式
請說明以下兩種擴展方式的差異。
// 方式 1:Interface extends
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// 方式 2:Type intersection
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string;
};
點擊查看答案
Interface extends:
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// 等價於
interface Dog {
name: string;
breed: string;
}
Type intersection:
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string;
};
// 等價於
type Dog = {
name: string;
breed: string;
};
差異:
- 語法:Interface 使用
extends,Type 使用& - 結果:兩者結果相同
- 可讀性:Interface 的
extends更直觀 - 靈活性:Type 的
&可以組合多個型別
範例:
// Interface:只能 extends 一個
interface Dog extends Animal {
breed: string;
}
// Type:可以組合多個
type Dog = Animal & Canine & {
breed: string;
};
題目 3:宣告合併
請說明以下程式碼的行為。
interface User {
name: string;
}
interface User {
age: number;
}
const user: User = {
name: 'John',
// 缺少 age 會如何?
};
點擊查看答案
interface User {
name: string;
}
interface User {
age: number;
}
// 兩個宣告會自動合併為:
// interface User {
// name: string;
// age: number;
// }
const user: User = {
name: 'John',
// ❌ 錯誤:Property 'age' is missing in type '{ name: string; }' but required in type 'User'
};
正確寫法:
const user: User = {
name: 'John',
age: 30, // ✅ 必須包含 age
};
宣告合併的應用場景:
// 擴展第三方套件的型別定義
interface Window {
myCustomProperty: string;
}
// 現在可以使用
window.myCustomProperty = 'value';
注意:Type Alias 不支援宣告合併
type User = { name: string; };
type User = { age: number; }; // ❌ 錯誤:Duplicate identifier
題目 4:實作(implements)
請說明 Interface 和 Type Alias 在類別實作上的差異。
// 情況 1:Interface
interface Flyable {
fly(): void;
}
class Bird implements Flyable {
fly(): void {
console.log('Flying');
}
}
// 情況 2:Type Alias
type Flyable = {
fly(): void;
};
class Bird implements Flyable {
fly(): void {
console.log('Flying');
}
}
點擊查看答案
兩者都可以用於 implements:
// Interface
interface Flyable {
fly(): void;
}
class Bird implements Flyable {
fly(): void {
console.log('Flying');
}
}
// Type Alias(物件型別)
type Flyable = {
fly(): void;
};
class Bird implements Flyable {
fly(): void {
console.log('Flying');
}
}
差異:
- Interface:傳統上更常用於定義類別的契約
- Type Alias:也可以使用,但語意上 Interface 更適合
建議:
- 定義類別契約時,優先使用 Interface
- 如果已經使用 Type Alias 定義,也可以實作
注意:Type Alias 定義的函式型別不能實作
type Flyable = () => void;
class Bird implements Flyable { // ❌ 錯誤:只能實作物件型別
// ...
}
5. Best Practices
最佳實踐
推薦做法
// 1. 定義物件結構時,優先使用 Interface
interface User {
name: string;
age: number;
}
// 2. 定義聯合型別時,使用 Type Alias
type Status = 'active' | 'inactive' | 'pending';
type ID = string | number;
// 3. 定義函式型別時,使用 Type Alias
type EventHandler = (event: Event) => void;
// 4. 需要宣告合併時,使用 Interface
interface Window {
customProperty: string;
}
// 5. 定義類別契約時,使用 Interface
interface Flyable {
fly(): void;
}
class Bird implements Flyable {
fly(): void {}
}