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的原创文章,尽在:"汪子熙":

相关文章
相关标签/搜索