Typescript mixins:如何访问混合类之间的属性?

Lau*_*ass 5 mixins typescript

我试图从官方 TS 文档中理解混音。

https://www.typescriptlang.org/docs/handbook/mixins.html

我在这里设立了一个游乐场。

我的问题是:我如何定义jump和duck来操作Sprite中定义的x和y属性?

即在这个例子中,我希望使用jump()和duck()来操作Sprite类中的x和y。那可能吗?将 x 和 y 属性添加到可跳和可鸭子中似乎很麻烦且重复。

一般来说,如果没有混合类之间属性的交叉操作,我很难看到 TS mixin 的真实用例。

代码是:

(() => {
  // 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)
        );
      });
    });
  }

  // Each mixin is a traditional ES class
  class Jumpable {
    jump() {
      console.log("jump");
    }
  }

  class Duckable {
    duck() {
      console.log("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();
  player.duck();
  console.log(player.x, player.y);
})();

Run Code Online (Sandbox Code Playgroud)

jca*_*alz 4

通过使用声明合并,您可以手动告诉编译器您的混入正在做什么,因为它本身无法弄清楚这一点。该文档将您正在做的事情称为“替代模式”,并且“此模式对编译器的依赖较少,而对代码库的依赖更多,以确保运行时和类型系统正确保持同步。”

因此,您应该期待一些额外的单调乏味的工作,让编译器相信您看似独立的JumpableDuckable可以访问x其中y未声明的属性。也许最简单的方法是为具有正确形状的东西定义一个接口:

interface Positioned {
  x: number,
  y: number
}
Run Code Online (Sandbox Code Playgroud)

然后告诉编译器jump()和方法旨在通过使用参数duck()来操作这种形状的东西:this

class Jumpable {
  jump(this: Positioned) {
    console.log("jump");
    this.y += 2;
  }
}

class Duckable {
  duck(this: Positioned) {
    console.log("duck");
    this.y -= 1;
  }
}
Run Code Online (Sandbox Code Playgroud)

这既可以让您在方法内部进行修改xy也可以在您尝试像常规方法一样使用 mixin 方法时发出警告:

const j = new Jumpable();
j.jump(); // compiler error
// The 'this' context of type 'Jumpable' is not 
// assignable to method's 'this' of type 'Positioned'.
Run Code Online (Sandbox Code Playgroud)

一旦你这样做了,你就可以证明一切都“有效”:

class Sprite {
  name = "";
  x = 0;
  y = 0;
  constructor(name: string) {
    this.name = name;
  }
}
interface Sprite extends Jumpable, Duckable { }
applyMixins(Sprite, [Jumpable, Duckable]);

const player = new Sprite("Player");
console.log(player.name, player.x, player.y); // Player 0 0
player.jump(); // jump
console.log(player.name, player.x, player.y); // Player 0 2
player.duck(); // duck
console.log(player.name, player.x, player.y); // Player 0 1
Run Code Online (Sandbox Code Playgroud)

如果您要使用替代模式,那就太好了。


推荐的替代 mixin 模式是使用类工厂函数,该函数使用标准类继承来通过 mixin 扩展基类。通过将基类限制Positioned对象的构造函数,您可以授予 mixin 访问基类xy属性的权限:

function Jumpable<TBase extends new (...args: any[]) => Positioned>(Base: TBase) {
  return class Jumpable extends Base {
    jump() {
      console.log("jump");
      this.y += 2;
    }
  };
}

function Duckable<TBase extends new (...args: any[]) => Positioned>(Base: TBase) {
  return class Duckable extends Base {
    duck() {
      console.log("duck");
      this.y -= 1;
    }
  };
}
Run Code Online (Sandbox Code Playgroud)

Jumpable和工厂函数内的类表达式Duckable允许jump()duck()访问this.y,因为Base构造函数属于 类型TBase,已知该类型可以构造 的某些子类型Positioned

现在,您无需手动将 mixin 方法应用于原型,只需在构造函数上调用 mixin 工厂函数即可:

class BaseSprite {
  name = "";
  x = 0;
  y = 0;
  constructor(name: string) {
    this.name = name;
  }
}
const Sprite = Jumpable(Duckable(BaseSprite));
Run Code Online (Sandbox Code Playgroud)

并注意不需要声明合并;编译器会自动理解Sprite实例Positioned还有jump()duck()方法:

const player = new Sprite("Player");
console.log(player.name, player.x, player.y); // Player 0 0
player.jump(); // jump
console.log(player.name, player.x, player.y); // Player 0 2
player.duck(); // duck
console.log(player.name, player.x, player.y); // Player 0 1
Run Code Online (Sandbox Code Playgroud)

Playground 代码链接