天天看點

TypeScript Mixins 概念介紹

除了傳統的 OO 層次結構,另一種從可重用元件建構類的流行方法是通過組合更簡單的部分類來建構它們。 您可能熟悉 Scala 等語言的 mixin 或特征的想法,并且該模式在 JavaScript 社群中也很流行。

模式依賴于使用具有類繼承的泛型來擴充基類。 TypeScript 最好的 mixin 支援是通過類表達式模式完成的。

看一個例子:

class Sprite {
  name = "";
  x = 0;
  y = 0;

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

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

// This mixin adds a scale property, with getters and setters
// for changing it with an encapsulated private property:

function Scale<TBase extends Constructor>(Base: TBase) {
  return class Scaling extends Base {
    // Mixins may not declare private/protected properties
    // however, you can use ES2020 private fields
    _scale = 1;

    setScale(scale: number) {
      this._scale = scale;
    }

    get scale(): number {
      return this._scale;
    }
  };
}

const EightBitSprite = Scale(Sprite);

const flappySprite = new EightBitSprite("Bird");
flappySprite.setScale(0.8);
console.log('Ethan:' ,flappySprite.scale);
      

本例子和我之前的文章其實很類似,隻是沒有使用裝飾器文法罷了。

使用Scale 對 Sprite 進行裝配,傳入的是 Class Sprite 的定義:

TypeScript Mixins 概念介紹

傳回的是一個新的函數,但隻要不使用該函數去執行個體化新的類執行個體,函數體就不會執行。

現在準備使用 Scale 裝飾過後的 Sprite 的擴充類去進行執行個體化操作了:

TypeScript Mixins 概念介紹

即将進入 mixin 内部:

TypeScript Mixins 概念介紹

首先執行基類的字段初始化邏輯:

TypeScript Mixins 概念介紹

然後才是子類字段的初始化邏輯:

TypeScript Mixins 概念介紹

Constrained Mixins

我們可以對上述 Mixins 做一些改造和增強。

在上面的形式中,mixin 沒有類的基礎知識,這會使建立你想要的設計變得困難。

比如,使用上面的 mixin,我們可以給任意的 Class 添加 _scale 屬性。

TypeScript Mixins 概念介紹
TypeScript Mixins 概念介紹

如果我們想對此做進一步限制,比如限制 Scale 隻能裝飾某些特定類型的 Class.

為了對此模組化,我們修改原始構造函數類型以接受泛型參數。

// This was our previous constructor:
type Constructor = new (...args: any[]) => {};
// Now we use a generic version which can apply a constraint on
// the class which this mixin is applied to
type GConstructor<T = {}> = new (...args: any[]) => T;
      

現在,使用這個類型構造器,必須傳入一個基類的類型作為類型參數:

TypeScript Mixins 概念介紹
type Spritable = GConstructor<Sprite>;
      

現在,Scale 裝飾器隻能修飾 Sprite 及其子類了:

TypeScript Mixins 概念介紹

現在,如果傳入一個并非 Sprite 及其子類的方法進入 Scale 裝飾器,會引起文法錯誤:

TypeScript Mixins 概念介紹

另一種通過 Object.defineProperty 實作的 Mixin

// Each mixin is a traditional ES class
class Jumpable {
  jump() {}
}

class Duckable {
  duck() {}
}

// Including the base
class Sprite {
  x = 0;
  y = 0;
}

// Then you create an interface which merges
// the expected mixins with the same name as your base
interface Sprite extends Jumpable, Duckable {}
// Apply the mixins into the base class via
// the JS at runtime
applyMixins(Sprite, [Jumpable, Duckable]);

let player = new Sprite();
player.jump();
console.log(player.x, player.y);

// This can live anywhere in your codebase:
function applyMixins(derivedCtor: any, constructors: any[]) {
  constructors.forEach((baseCtor) => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
          Object.create(null)
      );
    });
  });
}
      

把 Jumpable 和 Duckable 的屬性通過 Object.defineProperty 賦給 Sprite:

TypeScript Mixins 概念介紹

最後的運作時效果:

TypeScript Mixins 概念介紹

interface Sprite,可以使用 Duckable 和 Jumpable 類裡定義的方法了。

TypeScript Mixins 概念介紹

更多Jerry的原創文章,盡在:"汪子熙":

TypeScript Mixins 概念介紹