Разбираем миксины в TypeScript

В TypeScript нельзя наследовать или расширять более одного класса, но миксины позволяют обойти это ограничение.

Миксины создают разделяемые классы, которые можно объединять и, таким образом, формировать единый класс, содержащий все методы и свойства.

Примечание: В документации TypeScript подход, описанный в этом руководстве, называется альтернативным шаблоном.

В этом руководстве мы поговорим о том, как создавать и использовать миксины в TypeScript.

Ограничения классов

Чтобы продемонстрировать ценность миксинов, мы создадим следующий пример.

Предположим, у нас есть два класса, Car и Lorry, которые содержат методы drive и carry соответственно. Давайте рассмотрим третий класс под названием Truck. Он должен включать в себя оба метода, drive и carry:

export class Car {
  drive(name:string) {
    console.log(`This ${name} can drive very fast`);
  }
}

export class Lorry {
  carry(weight:number) {
    console.log(`This vehicle can carry ${weight} kg`);
  }
}

export class Truck extends Car, Lorry {}

Этот код не сработает, потому расширить можно только один класс.

error: Classes can only extend a single class

Чтобы решить эту проблему, мы можем использовать миксины.

Расширение класса интерфейса и слияние объявлений

Чтобы создать миксин, мы воспользуемся двумя функциями TypeScript. В этом разделе мы подробнее поговорим о них.

Расширение класса интерфейса

В отличие от классов, интерфейсы в TypeScript могут расширять несколько классов.

interface A extends ClassB, ClassC {}

Когда интерфейс расширяет класс, он расширяет только члены класса, но не их реализацию (потому что интерфейсы не содержат реализации).

Слияние объявлений

Когда вы создаете два или более объявления с одним и тем же именем, TypeScript объединяет их в одно.

interface Alligator {
  eyes: number;
  nose: number;
}

interface Alligator {
  tail: number;
}

const gator: Alligator = {
  eyes: 2,
  nose: 1,
  tail: 1
};

gator содержит свойства обоих интерфейсов Alligator.

Вспомогательная функция

Используя две вышеописанные функции TypeScript, мы можем создать интерфейс по имени Truck, и расширить классы Car и Lorry:

export class Truck {}
export interface Truck extends Car, Lorry {}

Из-за слияния объявлений класс Truck объединится с интерфейсом Truck. Это означает, что класс Truck теперь будет содержать определения функций из классов Car и Lorry.

Чтобы позволить классу Truck реализовать унаследованные от Car и Lorry функции, мы воспользуемся вспомогательной функцией из документации TypeScript.

В качестве первого аргумента функция принимает имя класса, в который мы хотим скопировать реализации (в нашем случае это Truck). В качестве второго аргумента она принимает массив классов, из которых мы хотим скопировать реализации (в нашем примере это Car и Lorry).

// the helper function
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)
      );
    });
  });
}

Получается вот что:

applyMixins(Truck, [Car, Lorry]);

Теперь мы можем получить доступ к методам в Car и Lorry из объекта truck.

const truck = new Truck();
truck.drive("truck");
truck.carry(10);

Этот код выдаст следующий результат:

This truck can drive very fast
This vehicle can carry 10 kg

Заключение

В этом мануале мы рассмотрели, как обойти одно из важных ограничений в TypeScript при помощи миксинов.

Читайте также: Преимущества TypeScript

Tags:

Добавить комментарий