[TypeScript翻译]TypeScript中的Mixin类

159 阅读5分钟

本文由 简悦SimpRead 转码,原文地址 mariusschulz.com

TypeScript 2.2引入了对静态类型混合类的支持。这篇文章简要地解释了mixi......

TypeScript旨在支持不同框架和库中使用的常见JavaScript模式。从TypeScript 2.2开始,mixin类就是这样一种模式,现在被静态地支持。这篇文章简要地解释了什么是混合类,然后继续展示了几个如何在TypeScript中使用混合类的例子。

#JavaScript/TypeScript中的混合器

一个混合类是一个实现了不同方面功能的类。其他的类可以包括混合类并访问其方法和属性。这样,mixin提供了一种代码重用的形式,它是基于_组合行为的。

[一个混合体是]一个函数,它

  1. 采用一个构造函数。
  2. 声明一个扩展该构造函数的类。
  3. 向该新类添加成员,并且
  4. 返回该类本身。

宣布TypeScript 2.2 RC

随着定义的结束,让我们深入了解一些代码。这里有一个Timestamped混合体,它在timestamp属性中跟踪一个对象的创建日期。

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

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    timestamp = Date.now();
  };
}

这里有很多事情发生。让我们先来剖析一下顶部的类型别名。

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

类型Constructor<T>是_construct签名_的别名,描述了一个可以构造通用类型T的对象的类型,其构造函数接受任意数量的任意类型的参数。它使用generic parameter default(从TypeScript 2.3引入)来指定T应该被视为{}类型,除非另有指定。

接下来,让我们看一下mixin函数本身。

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    timestamp = Date.now();
  };
}

这里我们有一个叫做Timestamped的函数,它接受一个叫做TBase的通用类型的Base参数。请注意,TBase被限制为与Constructor兼容,也就是说,该类型必须能够构造_东西。

在函数的主体中,我们创建并返回一个派生自Base的新类。这种语法一开始可能看起来有点奇怪。我们创建的是一个类表达式,而不是一个类声明,这是定义类的更常见的方式。我们的新类定义了一个名为 "timestamp "的单一属性,并立即指定了自UNIX纪元以来的毫秒数。

注意,从mixin函数返回的类表达式是一个_未命名的类表达式_,因为class关键字后面没有名字。与类声明相反,类表达式不一定要被命名。你可以选择添加一个名字,这个名字将是类主体的局部,并允许类指代自己。

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class Timestamped extends Base {
    timestamp = Date.now();
  };
}

现在我们已经涵盖了两个类型别名和混合函数的声明,让我们看看如何在另一个类中包含混合函数。

class User {
  name: string;

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

// Create a new class by mixing `Timestamped` into `User`
const TimestampedUser = Timestamped(User);

// Instantiate the new `TimestampedUser` class
const user = new TimestampedUser("John Doe");

// We can now access properties from both the `User` class
// and our `Timestamped` mixin in a type-safe manner
console.log(user.name);
console.log(user.timestamp);

TypeScript编译器理解我们在这里创建并使用了一个混集器。所有的东西都是完全静态类型化的,我们得到了通常的工具支持,如自动完成和重构。

#带有构造器的混合器

现在,让我们来看看一个稍微高级一点的混合器。这一次,我们要在混合器类中定义一个构造函数。

function Tagged<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    tag: string | null;

    constructor(...args: any[]) {
      super(...args);
      this.tag = null;
    }
  };
}

如果你在mixin类中定义了一个构造函数,它必须有一个类型为any[]的单一休息参数。这样做的原因是,mixin不应该被绑在一个已知构造函数参数的特定类上;因此mixin应该接受任意数量的任意值作为构造函数参数。所有的参数都被传递给Base的构造函数,然后混合器做它的事情。在我们的例子中,它初始化了tag属性。

我们使用Tagged混合器的方式与之前使用Timestamped的方式相同。

// Create a new class by mixing `Tagged` into `User`
const TaggedUser = Tagged(User);

// Instantiate the new `TaggedUser` class
const user = new TaggedUser("John Doe");

// We can now assign values to any property defined in either
// the `User` class or our `Tagged` mixin in a type-safe manner.
// TypeScript will type-check those assignments!
user.name = "Jane Doe";
user.tag = "janedoe";

#带方法的混合器

到目前为止,我们只在我们的混集函数中添加了数据属性。现在让我们来看看一个额外实现了两个方法的混合体。

function Activatable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    isActivated = false;

    activate() {
      this.isActivated = true;
    }

    deactivate() {
      this.isActivated = false;
    }
  };
}

我们将从我们的mixin函数中返回一个常规的ES2015类。这意味着你可以使用所有支持的类特性,如构造函数、属性、方法、getters/setters、静态成员等等。

再来看看我们如何在 "用户 "类中使用 "可激活 "混合函数。

const ActivatableUser = Activatable(User);

// Instantiate the new `ActivatableUser` class
const user = new ActivatableUser("John Doe");

// Initially, the `isActivated` property is false
console.log(user.isActivated);

// Activate the user
user.activate();

// Now, `isActivated` is true
console.log(user.isActivated);

#Composing multiple Mixins

一旦你开始组合混合器,混合器的灵活性就显现出来了。一个类可以包含任意多的混集函数。为了证明这一点,让我们把这篇文章中我们所看到的所有混合器组合起来。

const SpecialUser = Activatable(Tagged(Timestamped(User)));
const user = new SpecialUser("John Doe");

现在,我不确定 "SpecialUser "类是否非常有用,但重点是,TypeScript静态地理解这种混合成分。编译器可以对所有的使用进行类型检查,并在自动完成列表中建议可用的成员。

将此与类的继承进行对比,你会发现其中的区别。一个类只能有一个基类。从多个基类继承在JavaScript中是不可能的,因此在TypeScript中也不可能。

#进一步阅读


www.deepl.com 翻译