Многоцелевой декоратор TypeScript - PullRequest
0 голосов
/ 16 июня 2020

Я пытаюсь определить декоратор, который можно было бы использовать как для класса, так и для свойства. Код работает, но я не могу заставить работать определения типов. В настоящее время у меня есть следующее:

type Decorator<T> = T extends Function
  ? (target: T) => T
  : (target: T, propertyKey: string) => void;

function Schema<T>(): Decorator<T> {
  return (target: T, propertyKey?: string) => {
    if (target instanceof Function) {
      return target;
    }
  };
}

@Schema()
export default class MyClass {
  @Schema()
  bool: boolean;
}

Игровая площадка

Если я явно использую, установите generi c T на typeof BooleanProperty, это работает. Однако я хочу, чтобы Typescript мог определить тип и прекратить жаловаться.

Я также пробовал другие варианты, такие как перегрузка функций, но безуспешно.

Как мне это исправить?

1 Ответ

1 голос
/ 17 июня 2020

Вероятно, вы хотите, чтобы ваша фабрика декораторов была не-generi c, и чтобы она производила общий c декоратор. Сейчас ваша фабрика декораторов имеет тип generi c и возвращает не-generi c декоратор. В этом разница между этими двумя:

// bad
declare const genericFactoryForSpecificFunction: <T>() => (x: T) => T;
const oops = genericFactoryForSpecificFunction()(123); // unknown

// good
declare const specificFactoryForGenericFunction: () => <T>(x: T) => T;
const okay = specificFactoryForGenericFunction()(123); // 123

В плохом случае компилятор должен вывести T, когда вы вызываете genericFactoryForSpecificFunction(), но нет значения типа T, чтобы он мог проконсультироваться в это выражение. Таким образом, он выбирает unknown, что означает, что последующий вызов аргумента 123 возвращает unknown. В хорошем случае компилятор откладывает вывод T до вызова функции, возвращаемой specificFactoryForGenericFunction(). Это позволяет компилятору фактически использовать значение типа T для вывода T, и поэтому вы получаете 123 out.


После того, как вы внесете это изменение, вам понадобится Decorator type должен быть определенным c типом, представляющим общую подпись вызова функции c. Я полагаю, что можно перевести ваш условный тип в эту форму, но это немного беспорядочно и использует кортежи для отдыха:

type PossibleDecorator = <T>(
  target: T,
  ...args: T extends Function ? [] : [string?]
) => T extends Function ? T : void;

declare const pd: PossibleDecorator;
pd(RegExp); // okay, class decorator
pd({a: ""}, "a") // okay, prop decorator
pd(RegExp, "oops"); // error, tried to pass prop to class decorator

Я бы сказал, что перегрузка с несколькими вызовами-сигнатурами была бы лучше, и она имеет такое же поведение:

type Decorator = {
  <T extends new (...args: any) => any>(ctor: T): T; // class decorator
  <T, K extends keyof T>(proto: T, member: K): void; // prop decorator
}

declare const d: Decorator;
d(RegExp); // okay, class decorator
d({ a: "" }, "a") // okay, prop decorator
d(RegExp, "oops"); // error, tried to pass prop to class decorator

Итак, ваш примерный код будет выглядеть примерно так:

function Schema(): Decorator {
  return (target: any, propertyKey?: string) => {
    if (target instanceof Function) {
      return target;
    }
    return;
  };
}

@Schema()
export default class MyClass {
  @Schema()
  bool: boolean = false;
}

Выглядит хорошо.


Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...