Как определить универсальный тип значения свойства из оформленного свойства в декораторе - PullRequest
1 голос
/ 29 марта 2019

Я играю с некоторым кодом, который разрешает универсальный тип значения свойства и не позволяет предоставлять неправильное значение. Но когда я переключаюсь с TValue на (t: TValue) => TValue, тип TValue больше не разрешается. Теперь неизвестно {} тип, а не number больше

Пример без функции. Работает хорошо

type ProtoOf<T> = Pick<T, keyof T>;

function decorate<TValue>(value: TValue) {
  return <T extends { [KA in TKey]: TValue }, TKey extends keyof T>(
    proto: ProtoOf<T> & { [P in TKey]: TValue },
    propertyKey: TKey
  ) => {};
}

class Foo {
  // TS error: none
  // Result: EXPECTED
  @decorate(1) bar: number = 1;

  // TS Error:
  // Types of property 'wrongBar' are incompatible
  // Type 'number' is not assignable to type 'string'
  // Result: EXPECTED
  @decorate('') wrongBar: number = 1;
}

Пример с функцией. Не работает, как ожидалось

type ProtoOf<T> = Pick<T, keyof T>;

function decorate<TValue>(getValue: (t: TValue) => TValue) {
  return <T extends { [KA in TKey]: TValue }, TKey extends keyof T>(
    proto: ProtoOf<T> & { [P in TKey]: TValue },
    propertyKey: TKey
  ) => {};
}

class Foo {
  // TS Error: Operator '+' cannot be applied to types '{}' and '1'
  // Result: NOT EXPECTED: because we can assign `number` to `number`
  @decorate(v => v + 1) bar: number = 1;

  // TS error: none
  // Result: NOT EXPECTED: we should have error, we cannot assign `string` to `number`
  @decorate(v => v + '') wrongBar: number = 1;
}

Я ожидаю TValue равно number в примере с функцией, как в примере без функции

1 Ответ

1 голос
/ 29 марта 2019

Это известная проблема , как вам известно из ваших комментариев на GitHub. Подводя итог здесь:

В настоящее время вывод типов не работает так, как вы этого хотите, так как компилятор рассматривает оригинал как эквивалент чего-то вроде этого:

const barDeco = decorate(v => v + 1); // error
barDeco(Foo.prototype, "bar");
const wrongBarDeco = decorate(v => v + '');
wrongBarDeco(Foo.prototype, "wrongBar");

И вызовы decorate() в barDeco и wrongBarDeco не имеют достаточной информации о типе для компилятора, чтобы вывести общий тип, и поэтому он выводится как {}, что приводит к большой печали. Декоратор - это, по сути, карри-функция f(x)(y), и для исправления этого компилятор должен будет выводить тип f из типа y, что является новым видом контекстной типизации. Может быть, декораторы могут быть специально созданы для такого вывода; скорее всего, это будет серьезное изменение для карри-функций.

На данный момент единственный способ справиться с этим - вручную указать универсальный параметр при вызове декоратора, как в

class Foo {
  @decorate<number>(v => v + 1) bar: number = 1; // okay
  @decorate<number>(v => v + '') wrongBar: number = 1; // error
}

или для аннотирования вашего обратного вызова вручную, как в

class Foo {
  @decorate((v: number) => v + 1) bar: number = 1; // okay
  @decorate((v: number) => v + '') wrongBar: number = 1; // error
}

Эти обходные пути не оптимальны, но они работают, поэтому у вас есть какой-то способ справиться с вещами, если только и до тех пор, пока не будет решено Microsoft / TypeScript # 2607 . Есть много, много открытых вопросов, поэтому я не ожидал бы увидеть много движения по этому вопросу. Вероятность возрастет, если все больше людей займутся этим вопросом и дадут ему ? и опишут убедительные варианты использования и веские причины, по которым обходные пути недостаточны. Поскольку вы уже сделали это, я думаю, что вам не нужно больше ничего делать, кроме как двигаться дальше. Если будущие читатели заботятся об этом, они могут проверить проблему в GitHub и внести свой вклад.

Извините, нет лучшего ответа для вас. Удачи!

...