TypeScript - Mixins

Mixins 的介紹

你好,有抱負的程式設計師們!今天,我們將踏上一段令人振奮的旅程,探索 TypeScript Mixins 的世界。別擔心你以前從未聽過 mixins —— 到了這個教學的結尾,你會像專業DJ一樣能夠混合和匹配代碼!

TypeScript - Mixins

Mixins 是什麼?

想像你正在建造一個樂高城堡。你有不同的樂高積木可以組合,創造出令人驚奇的事物。在編程中,mixins 就像那些樂高積木。它們是我們可以“混合”到我們的類中以添加新功能的可重用代碼塊。

Mixins 解決的問題

在我們深入探讨 mixins 之前,讓我們了解一下我們為什麼需要它們。在面向對象的編程中,我們經常希望我們的對象具有多種行為。但 TypeScript,像許多其他語言一樣,不支持多重繼承。這意味著一個類只能從一個父類繼承。

舉個例子,假設我們有一個 Bird 類,我們想創建一個 Penguin。企鵝是鳥,但它們也會游泳。我們不能讓 Penguin 同時從 BirdSwimmer 類繼承。這就是 mixins 來救援的地方!

TypeScript 中的 Mixins 如何工作

在 TypeScript 中,mixins 是通過接口和函數的組合來實現的。讓我們一步一步來分解:

步驟 1:定義我們的基類

首先,我們將創建一個基類,我們的 mixin 將會擴展它:

class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}

步驟 2:創建 mixin 函數

接下來,我們將創建函數以為我們的基類添加行為:

type Constructor = new (...args: any[]) => {};

function Swimmer<TBase extends Constructor>(Base: TBase) {
return class extends Base {
swim() {
console.log(`${this.name} 正在游泳。`);
}
};
}

function Flyer<TBase extends Constructor>(Base: TBase) {
return class extends Base {
fly() {
console.log(`${this.name} 正在飛行。`);
}
};
}

讓我們分解一下:

  • type Constructor = new (...args: any[]) => {}; 定義了一個類型,表示任何構造函數。
  • 每個 mixin 函數都接受一個基類作為參數,並返回一個擴展它的新的類。
  • <TBase extends Constructor> 的部分確保我們的基類有一個構造函數。

步驟 3:應用 mixins 來創建新的類

現在,讓我們使用我們的 mixins 來創造一些令人驚奇的生物:

class Bird extends Animal {}
class Fish extends Animal {}

const FlyingFish = Swimmer(Flyer(Fish));
const SwimmingBird = Swimmer(Bird);

let nemo = new FlyingFish("Nemo");
nemo.swim(); // 輸出:Nemo 正在游泳。
nemo.fly();  // 輸出:Nemo 正在飛行。

let penguin = new SwimmingBird("Happy Feet");
penguin.swim(); // 輸出:Happy Feet 正在游泳。

這不是很棒嗎?我們創造了一個飛行的魚和一個游泳的鳥,而無需多重繼承!

高級 Mixin 技巧

帶屬性的 Mixin

Mixins 也可以為我們的類添加屬性:

function Aged<TBase extends Constructor>(Base: TBase) {
return class extends Base {
age: number = 0;
birthday() {
this.age++;
console.log(`生日快樂!${this.name} 現在 ${this.age} 歲了。`);
}
};
}

const AgingBird = Aged(Bird);
let tweety = new AgingBird("Tweety");
tweety.birthday(); // 輸出:生日快樂!Tweety 現在 1 歲了。
tweety.birthday(); // 輸出:生日快樂!Tweety 現在 2 歲了。

約束的 Mixin

有時候,我們希望我們的 mixins 只對某些類型的工作。我們可以使用約束來實現這一點:

interface Nameable {
name: string;
}

function Greeter<TBase extends Constructor & { new (...args: any[]): Nameable }>(Base: TBase) {
return class extends Base {
greet() {
console.log(`你好,我的名字是 ${this.name}!`);
}
};
}

const GreetingBird = Greeter(Bird);
let polly = new GreetingBird("Polly");
polly.greet(); // 輸出:你好,我的名字是 Polly!

在這個例子中,Greeter mixin 只能應用於有 name 屬性的類。

Mixin 的最佳實踐

  1. 保持 mixins 的專注:每個 mixin 應該添加一個特定的功能。
  2. 避免命名衝突:小心不要覆蓋現有的方法或屬性。
  3. 使用 TypeScript 的類型系統:利用接口和類型約束來確保類型安全。
  4. 為你的 mixins 撰寫文檔:清晰的文檔能夠幫助其他人了解如何使用你的 mixins。

結論

恭喜你!你剛剛學會了 TypeScript 最强大的功能之一 —— mixins。它們讓我們能夠從簡單、可重用的代碼片段組合成复雜的行为。記住,就像一個大師級的廚師混合食材一樣,優秀編程的關鍵在於知道何時以及如何結合不同的元素。

在你继续你的 TypeScript 旅程時,請繼續嘗試使用 mixins。試著創建自己的 mixins,看看它們如何能簡化你的代碼並使其更具彈性。快樂編碼,願你的 mixins 永遠完美融合!

方法 描述
Swimmer<TBase extends Constructor>(Base: TBase) 為類添加游泳能力
Flyer<TBase extends Constructor>(Base: TBase) 為類添加飛行能力
Aged<TBase extends Constructor>(Base: TBase) 為類添加年齡和生日功能
Greeter<TBase extends Constructor & { new (...args: any[]): Nameable }>(Base: TBase) 為具有名稱屬性的類添加問候功能

Credits: Image by storyset