Как объявить универсальный класс generic? - PullRequest
0 голосов
/ 10 января 2019

Я пытаюсь разработать способ объявления функции, которая может возвращать Promise, rxjs Observable или большинство Stream с определенным типом возвращаемого значения, но я не знаю правильного способа объявления этой функции в машинописном тексте.

В общем, я хочу в своем коде что-то вроде этого:

class Action<T> {
    dispatcher: Function
    constructor(dist) {
        this.dispatcher = dist
    }

    f = <T>(s: string): T<string> => this.dispatcher('hello'+s)
}

Таким образом я получаю ошибку: «T не является универсальным»

для меня T может быть Обещанием или Наблюдаемым или Потоком

Если бы у меня было что-то похожее на это, я могу объявить конкретного диспетчера, который возвращает значение так, как я хочу, в зависимости от того, как я его объявил

const promiseDispacher = (x:any)=>Promise.resolve(x)
const observableDispacher = (x:any)=>Observable.of(x)

То, что я хочу иметь, - это способность при вызове функции использовать по-разному

 const a = new Action<Promise>(promiseDistpacher)
 a.f('random string').then((s: string)=>{
   ....
 })

или

 const a = new Action<Observable>(observableDistpacher)
 a.f('random string').subscribe((s: string)=>{
   ....
 })

ОБНОВЛЕНО

f - это функция, которая выполняет некоторые операции и использует диспетчер для переноса результата в конкретный T (Promise или Stream) и возвращается обратно в класс, который выполняет функцию

1 Ответ

0 голосов
/ 10 января 2019

Я попытаюсь ответить на это, несмотря на то, что не совсем уверен, что вы делаете. Для начала давайте сделаем T универсальный тип, возвращаемый переданным диспетчером, что-то вроде Promise<any> или Observable<any> или ReadableStream<any>:

class Action<T> {
  dispatcher: (x: any) => T;
  constructor(dist: (x: any) => T) {
    this.dispatcher = dist
  }
  f = (s: string) => this.dispatcher('hello' + s);
}

const promiseDispatcher = (x: any) => Promise.resolve(x)
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
  // ...
}); // works

Теперь это работает, и тип f для Action<T> подразумевает тип (s: string) => T, а поскольку a является Action<Promise<any>>, то a.f(s) является Promise<any>. Обратите внимание, что несмотря на то, что f имеет T в своем типе возвращаемого значения, f сам по себе не является тем, что вы считаете универсальной функцией. Универсальный параметр для класса , и как только у вас есть конкретный экземпляр класса, его функция f также является конкретным типом функции.


Одна из проблем здесь заключается в том, что вы, вероятно, не хотите, чтобы a.f(s) был Promise<any>, а скорее увидел бы Promise<string>. Оказывается, поскольку TypeScript в настоящее время не очень поддерживает типы с более высоким родом , нет общего способа сказать, чтобы сделать T чем-то вроде Promise или Observable или ReadableStream, которые сами являются общими типами. С условными типами вы можете выбрать несколько жестко закодированных типов, таких как Promise<any>, Observable<any> и ReadableStream<any> и преобразовать их в Promise<string>, Observable<string> и ReadableStream<string> соответственно:

type AppliedToString<T> =
  T extends Promise<any> ? Promise<string> :
  T extends Observable<any> ? Observable<string> :
  T extends ReadableStream<any> ? ReadableStream<string> :
  T;

Теперь, если мы ограничим T этими жестко закодированными типами и воспользуемся функцией типа выше, мы должны получить что-то похожее на типирование, которое вы хотите для f:

// constrain T
class Action<T extends Promise<any> | Observable<any> | ReadableStream<any>> {
  dispatcher: (x: any) => T;
  constructor(dist: (x: any) => T) {
    this.dispatcher = dist
  }
  // assert return type of f as AppliedToString<T>
  f = (s: string) => this.dispatcher('hello' + s) as AppliedToString<T>;
}
const promiseDispatcher = (x: any) => Promise.resolve(x);
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
  // ...
}); // works

Теперь, если вы проверите тип возврата a.f, вы увидите, что это Promise<string>.

Недостатком такого способа является то, что вам нужно поддерживать жестко закодированный список, и он также не особенно безопасен для типов, поскольку он позволит вам передать диспетчер, который возвращает Promise<number>, и обрабатывать его как Promise<string> ... который взорвется во время выполнения.


Если бы я пытался быть более простым и более безопасным, я бы ограничил T до Promise<string>, Observable<string> или ReadableStream<string> и забыл бы отображение, но потребовал, чтобы переданный диспетчер принял string и вернуть T:

class Action<T extends Promise<string> | Observable<string> | ReadableStream<string>> {
  dispatcher: (x: string) => T;
  constructor(dist: (x: string) => T) {
    this.dispatcher = dist
  }
  f = (s: string) => this.dispatcher('hello' + s);
}
// note that x is type string in this:
const promiseDispatcher = (x: string) => Promise.resolve(x)
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
  // ...
})

Хорошо, надеюсь, что один или несколько из них помогут вам. Удачи!

...