Я пытаюсь реализовать миксины в TypeScript.

Вот код:

type Mixable = new (...args: any[]) => {};

class Base {
  constructor(protected _a: string, protected _b: string) {}
}

const A = (Base: Mixable) => class A extends Base {
  a() {
    console.log(this._a);
    return this;
  }
};

const B = (Base: Mixable) => class B extends Base {
  b() {
    console.log(this._b);
    return this;
  }
};

const C = A(B(Base));

const o = new C("a","b");

o.a().b();

Но, к сожалению, компилятор не может идентифицировать свойства, общие для миксинов, цепных методов и выдаваемых ошибок.

Вот площадка ссылка.

P.S. Если вы запустите это, он работает успешно и генерирует ожидаемый результат без каких-либо ошибок JS.

-1
A.N.M. Saiful Islam 3 Окт 2020 в 07:35

2 ответа

Лучший ответ

Для доступа к свойствам _a и _b вам необходимо обеспечить, чтобы класс, к которому вы применяете миксин, имел эти свойства.

Также миксины могут быть сложными в машинописном тексте со свойствами protected, поэтому я сделал их public.

interface HasAB {
  _a: string;
  _b: string;
}

type Mixable = new (...args: any[]) => HasAB

class Base {
  constructor(public _a: string, public _b: string) {}
}

Ссылка на игровую площадку Typescript

Это устраняет вашу проблему с доступом к свойствам.

Изменить:

Я играл с этим больше, и, к сожалению, миксины просто не работают с закрытыми свойствами. С этой установкой Сформированных классов могут получить доступ к защищенным свойствам переданного класса, но они выдают нам ошибку TS4094 «Свойство '_a' экспортированного выражения класса не может быть закрытым или защищенным». потому что свойства миксина должны быть общедоступными.

Я не достаточно уверен, чтобы сказать, что это невозможно, но на вашем месте я бы рассмотрел другой шаблон проектирования. Вы можете добавить методы доступа getA() и getB() в свой класс Base. Или вы можете определить свойства readonly public a и b, которые обращаются к базовым свойствам private _a и _b.

Вот как разбивается этот второй случай:

Мы определяем interface AB, у которого есть public свойства a и b. Напомним, что все свойства в интерфейсе машинописного текста по сути являются public.

interface AB {
  a: string;
  b: string;
}

Мы заявляем, что что-то считается Mixable, если это может быть создано с помощью ключевого слова new и если этот созданный объект содержит a и b.

type Mixable = new (...args: any[]) => AB;

Мы изменяем Base так, чтобы он реализовал interface AB, что сделает его Mixable. Вы можете явно написать class Base implements AB, но не обязаны. Его можно будет присвоить Mixable, если он может читать свойства a и b. Мы используем геттеры javascript для реализации a и b как свойств readonly, которые будут читать частные значения _a и _b, но не могут их изменять.

class Base {
  constructor( protected _a: string, protected _b: string) {}

  get a(): string {
    return this._a;
  }

  get b(): string {
    return this._b;
  }
}

Теперь о наших миксинах. Мы говорим, что смешиваемый класс должен иметь тип Mixable. Мы используем общий <T extends Mixable>(Mixed: T), чтобы знать, что возвращаемый класс будет иметь все свойства и методы переданного класса, а не только те, что в Mixable. Это устраняет проблемы с цепочкой.

Обратите внимание, что я переименовал методы в вашем примере, потому что я использовал a и b в качестве имен свойств, поэтому я также не могу использовать их в качестве имен методов.

const A = <T extends Mixable>(Mixed: T) => class A extends Mixed {
  aMethod() {
    console.log(this.a);
    return this;
  }
};

const B = <T extends Mixable>(Mixed: T) => class B extends Mixed {
  bMethod() {
    console.log(this.b);
    return this;
  }
};

Теперь у вас больше нет проблем ни с чем из этого:

const C = A(B(Base)); // fine because Base extends Mixable

const o = new C("a","b");

o.aMethod().bMethod(); // fine because o.aMethod() returns an object with all of the abilities of C.

Ссылка на игровую площадку Typescript

2
Linda Paiste 4 Окт 2020 в 21:13

Спасибо, ребята, за ваш вклад. Это добавило огромной ценности вопросу.

Прежде чем я продолжу, позвольте мне объяснить свое первоначальное намерение и недоразумение, возникшее из-за моего кода.

Моя основная цель при использовании миксинов - разложить мои большие классы . Поскольку это будет применяться в разных классах, я создаю вспомогательный модуль. Чтобы пример был понятен, я опубликовал более простую реализацию.

Что касается идентификатора Base, я не имел в виду, что аргумент Base относится к базовому классу, а к родительскому классу (то есть к базе расширения). В любом случае, чтобы избавиться от путаницы, я буду использовать вместо него идентификатор Parent.

Итак, вот что я придумал, и считаю, что его можно в значительной степени расширить. Не обращайте внимания на странное имя Libbable (это просто для демонстрации идеи).


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

interface Libbable {
  x: string;
  a(): any;
}

type Mixin = Mixable<Libbable>;

class Lib {
  constructor(public x: string) {}
  a(): this { return this }
}

const A = <T extends Mixin>(Parent: T) => class extends Parent {
  a() {
    console.log(this.x);
    return this;
  }
};

const B = <T extends Mixin>(Parent: T) => class extends Parent {
  b() {
    return this.a();
  }
};

const L = A(B(Lib));
const o = new L("x");

o.a().b();

Ссылка на площадку


Из-за проблемы дизайна в настоящее время TypeScript не может совместно использовать закрытые свойства среди миксинов. Я очень жду возможности использовать это, иначе мощные модификаторы доступа здесь просто бесполезны.

Поскольку необходимо объявить методы в базовом классе, чтобы использовать их в миксинах, они будут вызываться после вызова соответствующих методов класса миксинов. Это не то, чего я хочу. JS позволяет нам избавиться от этого, а TS - нет. Если у вас есть идея получше достичь того, чего я хочу, не стесняйтесь делиться.

0
A.N.M. Saiful Islam 7 Окт 2020 в 06:18