Декоратор свойств TypeScript, который принимает параметр оформленного типа свойства - PullRequest
0 голосов
/ 08 марта 2020

Я пытаюсь написать Default декоратор, чтобы это работало:

class A {
  @Default(() => new Date())
  test: Date;
}

Я пытаюсь добиться строгой типизации, чтобы параметр декоратора был (1) из декорированного тип свойства или (2) функция без параметров, возвращающая значение декорированного типа свойства.

Я пробовал следующее:

type DefaultParam<T> = T | (() => T);

export function Default<K extends string, T extends Record<K, V>, V>(param: DefaultParam<V>): (t: T, p: K) => void {
  return (target: T, propertyKey: K) => {
    // Probably irrelevant
  };
}

Это, однако, не с:

Argument of type 'A' is not assignable to parameter of type 'Record<string, Date>'.
  Index signature is missing in type 'A'.

Задание параметров типа, однако, работает как ожидалось:

class A {
  @Default<'test', A, Date>(() => new Date())
  test: Date;
}

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

1 Ответ

1 голос
/ 09 марта 2020

Я бы сказал, что главная проблема здесь в том, что функция декоратора с карри не может определить спецификацию для параметра типа K (или параметра типа T) при его использовании. Если у вас есть функция curry generi c с типом, подобным:

declare const curryBad: <T, U>(t: T) => (u: U) => [T, U]

, компилятор попытается вывести T и U при ее вызове. Допустим, вы звоните curryBad(1). Значение 1 приводит к выводу T как number. Но здесь нет значения типа U, поэтому вывод типа завершается неудачно для U и становится unknown. Поэтому результат curryBad(1) равен (u: unknown) => [number, unknown]:

const bad = curryBad(1)(""); // [number, unknown]

Хотя не исключено, что компилятор может теоретически отложить вывод U до вызова возвращаемой функции, это не так, как он работает на практике , Вместо этого вы можете просто написать сигнатуру функции для начала: не объявляйте U как параметр исходной функции; вместо этого объявите его как параметр возвращаемой функции:

declare const curryGood: <T>(t: T) => <U>(u: U) => [T, U]

Теперь вызов curryGood(1) вернет значение типа <U>(u: U) => [number, U], что, вероятно, то, что вы хотите:

const good = curryGood(1)(""); // [number, string]

Имея это в виду, я бы предложил переместить ваш параметр K. Кроме того, я не уверен, что вам действительно нужно, чтобы T был его собственным типом generi c; Record<K, V> может быть достаточно хорошо. Но если вы обнаружите, что вам это нужно, его также следует переместить вместе с K:

export function Default<V>(
  param: DefaultParam<V>): <K extends PropertyKey>(t: Record<K, V>, p: K) => void {
  return <K extends PropertyKey>(target: Record<K, V>, propertyKey: K) => {    
  };
}

И теперь ваш декоратор должен работать, как нужно:

class Good {
  @Default(() => new Date()) // no error
  test!: Date;
}

class Bad {
  @Default(123) // error! Date is not number
  test!: Date; 
}

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

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

...