TypeScript Mixins 概念介紹

Mixinshtml

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

模式依賴於使用具備類繼承的泛型來擴展基類。 TypeScript 最好的 mixin 支持是經過類表達式模式完成的。app

看一個例子:函數

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);

本例子和我以前的文章TypeScript 類裝飾器的一個例子和使用單步調試搞清楚其運行原理其實很相似,只是沒有使用裝飾器語法罷了。this

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

返回的是一個新的函數,但只要不使用該函數去實例化新的類實例,函數體就不會執行。.net

如今準備使用 Scale 裝飾事後的 Sprite 的擴展類去進行實例化操做了:prototype

即將進入 mixin 內部:設計

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

而後纔是子類字段的初始化邏輯:

Constrained Mixins

咱們能夠對上述 Mixins 作一些改造和加強。

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

好比,使用上面的 mixin,咱們能夠給任意的 Class 添加 _scale 屬性。

若是咱們想對此作進一步限制,好比限制 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;

如今,使用這個類型構造器,必須傳入一個基類的類型做爲類型參數:

type Spritable = GConstructor<Sprite>;

如今,Scale 裝飾器只能修飾 Sprite 及其子類了:

如今,若是傳入一個並不是 Sprite 及其子類的方法進入 Scale 裝飾器,會引發語法錯誤:

另外一種經過 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:

最後的運行時效果:

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

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

相關文章
相關標籤/搜索