Правильная защита типов пользовательских конвейерных операторов RxJS - PullRequest
0 голосов
/ 04 декабря 2018

Я работаю с API, который имеет фиксированную, непротиворечивую структуру ответов: это всегда объект, у которого есть свойство data.Поскольку это очень утомительно и слишком явно для постоянного отображения данных в запросах RxJS (или эффектов ngrx), я решил ввести пользовательский оператор RxJS, который собирает данные и применяет необязательный обратный вызов.

Но теперь некоторые измои эффекты жалуются на информацию о типе (например: property x doesn't exist on type {}), поэтому я предполагаю, что моих усилий по правильной защите ввода-вывода оператора недостаточно:

export function mapData<T, R>(callback?: (T) => R) {
  return (source: Observable<T>) => source.pipe(
    map(value => value['data'] as R), // isn't that an equivalent of `pluck<T>('data')` ?
    map(value => typeof callback === 'function' ? callback(value) : value as R),
  );
}

Пример эффекта ngrx спроблемы охранника типа:

switchMap(() => this.api.getData().pipe(
  mapData(),
  mergeMap(data => [
     new actions.DataSuccessAction({ id: data.id }), // <-- id does not exist on type {}
     new actions.SomeOtherAction(data),
  ]),
  catchError(err => of(new actions.DataFailureAction(err))),
)),

Что, конечно же, исчезнет, ​​когда я приведу это явным образом:

mapData<any, IMyData>(....),

Мне бы хотелось услышать, является ли это правильным способом TypeScript дляделать вещи.

1 Ответ

0 голосов
/ 04 декабря 2018

Вы можете использовать несколько перегрузок для моделирования различных типов поведения.Я не уверен на 100%, каково должно быть поведение, это не на 100% ясно из вашего вопроса, но мое прочтение подсказывает следующие правила:

  1. Если T имеет data и нетcallback задано return data
  2. Если мы укажем callback, тогда возвращение продиктовано callback
  3. Если не указано callback и 1 не применяется, просто returnT

Перегруженная версия будет выглядеть примерно так:

export function mapData<T, R>(callback: (data: T) => R) : OperatorFunction<T, R>
export function mapData<T extends { data: any }>() : OperatorFunction<T, T['data']>
export function mapData<T>() : OperatorFunction<T, T>
export function mapData<T extends { data? : undefined } | { data: R }, R>(callback?: (data: T) => R) {
  return (source: Observable<T>) => source.pipe(
    map(value => typeof callback === 'function' ? callback(value) : (value.data ? value.data : value)),
  );
}

// Tests
of({ data: { id: 0 }}).pipe(
  mapData(),
  mergeMap(data => [
    new actions.DataSuccessAction({ id: data.id }), // <-- id does not exist on type {}
    new actions.SomeOtherAction(data),
  ]),
  catchError(err => of(new actions.DataFailureAction(err))),
)

of({ other: { id: 0 }}).pipe(
  mapData(d =>d.other),
  mergeMap(data => [
    new actions.DataSuccessAction({ id: data.id }), // <-- id does not exist on type {}
    new actions.SomeOtherAction(data),
  ]),
  catchError(err => of(new actions.DataFailureAction(err))),
)

of({ data: { id: 0 }}).pipe(
  mapData(d =>d.data),
  mergeMap(data => [
    new actions.DataSuccessAction({ id: data.id }), // <-- id does not exist on type {}
    new actions.SomeOtherAction(data),
  ]),
  catchError(err => of(new actions.DataFailureAction(err))),
)



// Filler classes
namespace actions {
  export class DataSuccessAction<T>{
    constructor(public data:T){}
  }
  export class SomeOtherAction<T>{
    constructor(public data:T){}
  }

  export class DataFailureAction<T>{
    constructor(public data:T){}
  }
}
...