Хитрый класс декоратора - PullRequest
       87

Хитрый класс декоратора

0 голосов
/ 18 сентября 2018

У меня есть следующий фрагмент кода, который мне довольно трудно понять:

  export class Record{

  };

  export class RecordMissingExtendsError{
      constructor(r:any){

      }
  }


  export function Model() {
    return <T extends { new(...args: any[]): {} }>(ctr: T) => {
        if (!(ctr.prototype instanceof Record)) {
            throw new RecordMissingExtendsError(ctr);
        }

        return (class extends ctr {
            constructor(...args: any[]) {
                const [data] = args;
                if (data instanceof ctr) {
                    return data;
                }
                super(...args);
                (this as any)._completeInitialization();
            }
        });
    };
}

Мне трудно разобраться в приведенном выше коде и понять так же, как следующее:

Модель возвращает тип T ( Я знаю, что такое дженерики, поэтому не беспокойтесь об объяснении дженериков ), в которых

T extends { new(...args: any[]): {}

Что означает выше?Вы собираетесь сохранить существующие свойства и дополнительные функции?

Кроме того, вы можете объяснить тип возвращаемого значения функции?Добавляем ли мы дополнительный конструктор в T?

(class extends ctr {
            constructor(...args: any[]) {
                const [data] = args;
                if (data instanceof ctr) {
                    return data;
                }
                super(...args);
                (this as any)._completeInitialization();
            }
        });

Ответы [ 2 ]

0 голосов
/ 18 сентября 2018

Ограничение типа

T extends { new(...args: any[]): {} }

Здесь тип T ограничен любым типом, расширяющим { new(...args: any[]): {} }.Форматирование здесь может быть немного запутанным - при правильном форматировании тип выглядит следующим образом:

{
    new(...args: any[]): {}
}

Это описывает так называемый newable , который является своего рода функциональным объектом, который должен быть вызван с помощью new.Например:

let A: { new(): any; };
A(); // not ok
new A(); // ok

let B: { new(foo: string): any; };
B(); // not ok
new B(); // not ok, param missing
new B('bar'); // ok

...args: any[] - это просто объявление параметра rest , объявление возвращаемого типа, {} означает, что объект должен быть возвращен.TypeScript будет предполагать, что возвращенный объект не имеет свойств вообще .

Возвращаемый анонимный класс

Что касается возвращаемого типа: Поскольку функция декоратора Model является декоратором класса, он может вернуть сам класс.Если он возвращает класс, этот класс будет использоваться вместо декорированного класса.

Если декоратор класса возвращает значение, он заменит объявление класса предоставленной функцией конструктора.

- из справочника TS

Например:

// `ctr` is a common abbreviation for "constructor"
function Decorate(ctr: Function) {
    return class {
        constructor() {
            super();
            console.log('decorated');
        }
    };
}

@Decorate
class A {
    constructor() {
        console.log('A');
    }
}

new A(); // this will log: "A" first, then "decorated"
0 голосов
/ 18 сентября 2018

T extends { new(...args: any[]): {} } означает, что T должна быть функцией конструктора (т.е. классом).Аргументы конструктора, а также тип возвращаемого значения не имеют значения (T может иметь любое количество аргументов и может возвращать любой тип, который расширяет {}, фактически любой тип объекта).

Если вызывается напрямую, T будет классом.Подход, используемый для ввода этого декоратора, в основном состоит из миксинов (описан для машинописи здесь ).

Возвращаемое значение функции будет новым классом, которыйнаследует декорированный класс.Таким образом, это не добавление конструктора, а замена оригинального конструктора новым и вызов оригинального конструктора с помощью вызова super.

Обобщения, на мой взгляд, немного излишни в этой ситуации.Они полезны для миксинов, потому что они перенаправляют исходный класс из входного параметра в выходной параметр (а миксин добавляет члены к типу).Но поскольку декораторы не могут изменить структуру типа, пересылать нечего.Кроме того, вместо конструктора, возвращающего {}, я бы набрал его, чтобы он возвратил Record, поскольку вы проверяете это во время выполнения, может также проверить это и во время компиляции:

export class Record{
    protected _completeInitialization(): void {}
};

export function Model() {
  return (ctr: new (...a: any[]) => Record ) => {
      if (!(ctr.prototype instanceof Record)) {
          throw new RecordMissingExtendsError(ctr);
      }

      return (class extends ctr {
          constructor(...args: any[]) {
              const [data] = args;
              if (data instanceof ctr) {
                  return data;
              }
              super(...args);
              this._completeInitialization(); // no assertion since constructor returns a record
          }
      });
  };
}

@Model()
class MyRecord extends Record { }

@Model()// compile time error, we don't extend Record
class MyRecord2  { }
...