Несовместимость перегрузки функции Typescript - PullRequest
1 голос
/ 28 апреля 2020

Я внедрил хранилище NgRx и обнаружил, что использую шаблон

 concatMap(action => of(action).pipe(
          withLatestFrom(this.store.pipe(select(fromBooks.getCollectionBookIds)))
        )),

(снизу https://ngrx.io/guide/effects)

Учитывая, сколько в нем места может поднять, я хотел написать удобный оператор утилиты для его обработки.

Итак, я пришел к реализации, которая должна работать:

export function concatMapLatestFrom<A extends Action>(
    ...xs: Array<ObservableInput<never>>
): OperatorFunction<A, unknown> {
    return function (source: Observable<A>): Observable<unknown> {
        return source.pipe(
            concatMap((action) => of(action).pipe(withLatestFrom(...xs))),
        );
    };
}

Я написал несколько правильно типизированных перегрузок:

export function concatMapLatestFrom<X1, A extends Action>(
    source1: ObservableInput<X1>,
): { (source1: X1): OperatorFunction<A, [A, X1]> };

export function concatMapLatestFrom<X1, X2, A extends Action>(
    source1: ObservableInput<X1>,
    source2: ObservableInput<X2>,
): { (source1: X1, source2: X2): OperatorFunction<A, [A, X1, X2]> };

export function concatMapLatestFrom<X1, X2, X3, A extends Action>(
    source1: ObservableInput<X1>,
    source2: ObservableInput<X2>,
    source3: ObservableInput<X3>,
): {
    (source1: X1, source2: X2, source3: X3): OperatorFunction<
        A,
        [A, X1, X2, X3]
    >;
};

Но по какой-то причине он считает, что сигнатура перегрузки не совместима с реализацией. Если я закомментирую один из них, он повторяется для следующего в строке.

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

Ответы [ 2 ]

1 голос
/ 28 апреля 2020

Я думаю, преступники - это типы never и unknown, которые вы там используете.

Итак, если у вас есть

of(1)
  .pipe(
    concatMapLatestFrom(of('john'), of(true))
  ).subscribe(observer)

, тогда если я правильно понял, observer должен получить [number, string, boolean] кортеж.

Насколько я могу судить, concatMapLatestFrom не говорит это очень четко:

function concatMapLatestFrom (): OperatorFunction<A, unknown> {}

Итак, у нас есть только первый элемент этого кортежа, который является типом источника. Осталось бы сделать вывод о свойствах других типов, основываясь на типах ObservableInput, предоставленных concatMapLatestFrom.

, с учетом этого, вот мой подход:

export function concatMapLatestFrom<
  A extends Action,
  U extends Array<ObservableInput<any>>,
  R = Unshift<ObservedValueTupleFromArray<U>, A>
> (
  ...xs: U
): OperatorFunction<A, R> {
  return function (source: Observable<A>): Observable<R> {
    return source.pipe(
      concatMap((action) => of(action).pipe(withLatestFrom(...xs))),
    );
  };
}

Используемые типы можно экспортировать из 'rxjs':


// Mapped tuples: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-1.html#mapped-types-on-tuples-and-arrays
// E.g [Observable<number>, Observable<string>] --> [number, string]
export type ObservedValueTupleFromArray<X> =
  X extends Array<ObservableInput<any>>
  ? { [K in keyof X]: ObservedValueOf<X[K]> }
  : never;

// Simply extract the value
export type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;

// Unshift<[B, C], A> --> [A, B, C]
export type Unshift<X extends any[], Y> =
  ((arg: Y, ...rest: X) => any) extends ((...args: infer U) => any)
  ? U
  : never;

Источник

0 голосов
/ 28 апреля 2020

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

export function concatMapLatestFrom(
  ...xs: Array<ObservableInput<never>>
) {
  return function(source: Observable<any>): Observable<unknown> {
    return source.pipe(
      concatMap(action => of(action).pipe(withLatestFrom(...xs)))
    );
  };
}
...