Развлечения с марками. Эпизод 16. Смешанные классы TypeScript против штампов

Привет. Я разработчик Василий Боровяк, и добро пожаловать в шестнадцатый выпуск Василия Боровяка, представляющего Fun with Stamps.

TL;DR

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

Что такое смешанные классы TypeScript?

Недавно TypeScript v2.2 выпустил новую классную функцию - смешивание классов. По сути, теперь вы можете «смешивать классы». Именно для этого и предназначено марки - «смешивание марок».

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

В этом выпуске единственное, что я хотел бы отметить, это то, что функцию «смешивания классов» трудно читать. Конечно, это зависит от ваших навыков TypeScript. Но давайте представим, что вы посредственный ленивый разработчик, впервые читающий код.

Вот официальный пример из Примечаний к выпуску TypeScript v2.2.

Сколько времени нужно, чтобы понять, что здесь происходит?

Давайте перепишем его штампами.

Сколько времени вам, моему воображаемому посредственному разработчику, нужно, чтобы разобраться в приведенном ниже коде?

import stampit from '@stamp/it';
const Point = stampit({
  init ({x, y}) {
    this.x = x;
    this.y = y;
  }
});
const Person = stampit({
  init ({name}) {
    this.name = name;
  }
});
const Tagged = stampit({ 
  init ({_tag}) {
    this._tag = _tag;
  }
});
const TaggedPoint = stampit(Point, Tagged);
let point = TaggedPoint({x: 10, y: 20, _tag: 'hello'});
const Customer = stampit(Person, Tagged, {
  init ({accountBalance = 0}) {
    this.accountBalance = accountBalance;
  }
});
let customer = Customer({accountBalance: 0, _tag: 'test'});

В штампах вы можете предоставить все аргументы инициализатору (он же конструктор). В TypeScript вам нужно назначить свойства после, что вы создали объект.

Но самое главное, что более читаемо вам?

Фэнтезийный синтаксис TypeScript

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

Пример синтаксиса:

stamp Point {
  constructor({public x: number, public y: number}) {}
}
stamp Person {
  constructor({public name: string}) {}
}
stamp Tagged {
  constructor({public _tag: string}) {}
}
stamp TaggedPoint extends Point, Tagged;
let point = TaggedPoint({x: 10, y: 20, _tag: 'hello'});
stamp Customer extends Person, Tagged, {
  constructor({public accountBalance: number = 0}) {}
};
let customer = Customer({accountBalance: 0, _tag: 'test'});

Хорошее начало! Давайте еще пофантазируем. Преобразуем простые штампы из самого первого эпизода Fun with Stamps. Свойства, методы, инициализаторы:

stamp HasFoo {
  public foo: number = 'default foo';
}
stamp PrintFoo {
  public printFoo() {
    console.log(this.foo || 'There is no foo');
  }
}
stamp InitFoo {
  constructor({public foo: string}) {}
}
stamp Foo extends HasFoo, PrintFoo, InitFoo;    // This should work,
const Foo = stamp(HasFoo, PrintFoo, InitFoo);   // and this too.
const obj = Foo('incoming foo!');
obj.printFoo(); // incoming foo!

А как насчет статики?

stamp TraceStampMetaData {
  public static trace() {
    console.log(`This stamp consists of ${this.stamp}`);
  }
});
stamp Foo2 extends Foo, TraceStampMetaData;    // These two lines
const Foo3 = stamp(Foo, TraceStampMetaData);   // are identical.
Foo2.trace();   // prints Foo2.stamp meta-data
Foo3.trace();   // prints Foo3.stamp meta-data

Было бы замечательно, если бы разработчики могли выбирать между синтаксисом stamp() и extends по своему усмотрению. Точно так же, как мы можем выбирать между синтаксисом .then() и await в ES6.

Остальные части TypeScript должны работать со штампами так же, как они работают с классами (дженерики, интерфейсы и т. Д.).

Это очень привлекательно. Этот синтаксис убедит меня перейти на TypeScript на постоянной основе.

Различия между классами TypeScript и штампами

  1. Благодаря штампам синтаксис становится проще и короче.
  2. Со штампами у вас может быть столько конструкторов, сколько захотите. Все получат точно такие же аргументы.
  3. В штампах нет цепочки наследования - это основная причина того, что синтаксис намного проще. Все методы относятся к __proto__ экземплярам ваших объектов по соображениям производительности.
  4. Вы можете использовать множественное наследование.
  5. Марки не требуют ключевого слова new. Таким образом, при необходимости его можно заменить простой заводской функцией.
  6. С марками у вас есть прямой доступ к метаданным вашей марки. Посмотреть здесь". Имея эту информацию, вы можете создавать более умные вещи.
  7. instanceof не работает со штампами по дизайну. (Если это действительно необходимо, я бы порекомендовал реализовать ключевое слово TypeScript obj is Foo как obj.__proto__ === Foo.stamp.methods. Но лучше вообще не использовать его.)
  8. … Я изо всех сил пытался найти недостатки штампованного подхода, но у меня ничего не вышло. Пожалуйста, помогите мне…

Удачи с марками!