Интересная история о выводе типов с помощью Rx Js и машинописного текста - PullRequest
3 голосов
/ 28 мая 2020

У меня есть следующий код

  let obs$: Observable<string>
   obs$.pipe(
        tap(data => {
          // do stuff
        }),
      );

Если я использую VSCode и наведу курсор на переменную data, я вижу, что это тип string, как и ожидалось.

Тогда я добавить ingnoreElements после tap

obs$.pipe(
    tap(data => {
       // do stuff
    }),
    ignoreElements()
  );

Теперь, если я наведу курсор на data, вывод типа говорит мне, что data имеет тип any.

Это кажется как ретроактивный вывод. В чем причина такого поведения вывода типа?

Здесь stackblitz , чтобы воспроизвести эту ситуацию.

ОТВЕТ, предоставленный ответом от @Andrei Gătej

Весь тайна лежит в способе объявления ignoreElements(), т.е.

export declare function ignoreElements(): OperatorFunction<any, never>;

Теперь вы помещаете ignoreElements() после оператора, такого как tap(), который объявлен следующим образом

export declare function tap<T>(observer: PartialObserver<T>): MonoTypeOperatorFunction<T>;

и в основном внутри pipe() у вас есть такая цепочка, как эта

MonoTypeOperatorFunction<T>,
OperatorFunction<any, never>

, которая pipe() интерпретируется как: «T» - это общий c тип, «any» - реальный тип , тогда, поскольку ignoreElements () ожидает «any», то «T» должен иметь тип «any»

И это объясняет загадку.

Теперь есть несколько возможных решений. Один из способов - выделить ignoreElements() в другой pipe(), например,

obs$.pipe(
    tap(data => {
       // do stuff
    }),
  )
  .pipe(
    ignoreElements()
  );

Если вы сделаете это, переменная data будет выведена с типом string, что нам и нужно.

Более элегантным решением было бы изменить объявление ignoreElements(). Если вы объявите это так:

export declare function ignoreElements<T>(): OperatorFunction<T, never>;

, все будет работать, как ожидалось, без необходимости выполнять какие-либо трюки.

Возможно, стоит предложить изменить объявление ignoreElements().

1 Ответ

3 голосов
/ 28 мая 2020

Это очень интересный вопрос!

Давайте посмотрим на типы ignoreElements():

export function ignoreElements(): OperatorFunction<any, never> {
  return function ignoreElementsOperatorFunction(source: Observable<any>) {
    return source.lift(new IgnoreElementsOperator());
  };
}

Source .

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

export interface UnaryFunction<T, R> { (source: T): R; }

export interface OperatorFunction<T, R> extends UnaryFunction<Observable<T>, Observable<R>> {}

Источник .

Итак, UnaryFunction<T, R> описывает функцию, которая получает аргумент типа T, а возвращает что-то типа R.

Аналогично, OperatorFunction<T, R> - это функция, которая принимает аргумент Observable типа T и возвращает Observable типа R.


Возвращаясь к типам ignoreElements() : OperatorFunction<any, never>. Итак, это означает, что ignoreElements возвращает функцию , аргументом которой является Observable<any>, а тип возвращаемого значения - Observable<never>.

Теперь давайте посмотрим на типы pipe():

pipe(): Observable<T>;
pipe<A>(op1: OperatorFunction<T, A>): Observable<A>;
pipe<A, B>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>): Observable<B>;
pipe<A, B, C>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>, op3: OperatorFunction<B, C>): Observable<C>;

Источник .

Каждый оператор возвращает OperationFunction. Например, из

pipe<A, B>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>): Observable<B>;

мы можем видеть, что тип возвращаемого значения первого оператора является типом ввода для второго оператора и так далее. Вот как выводятся типы.

Кроме того, T может быть выведено из constructor Observable:

constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
  if (subscribe) {
    this._subscribe = subscribe;
  }
}

class Subscriber<T> implements Observer<T> { /* ... */ }

export interface Observer<T> {
  closed?: boolean;
  next: (value: T) => void; // ! 
  error: (err: any) => void;
  complete: () => void;
}

Source .

...