0

我正在使用一个具有固定、一致的响应结构的 API:它始终是一个具有data属性的对象。由于在 RxJS 请求(或 ngrx 效果)中不断映射数据非常烦人且过于明确,因此我决定引入一个自定义 RxJS 操作符来提取数据并应用可选的回调。

但现在我的一些效果抱怨类型信息(如property x doesn't exist on type {}:),所以我想我努力正确地对操作员的 I/O 进行类型保护是不够的:

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 做事方式。

4

1 回答 1

1

您可以使用多个重载来模拟不同的类型行为。我不是 100% 确定行为应该是什么,从你的问题中也不是 100% 清楚,但我对它的阅读表明了以下规则:

  1. 如果Tdata且没有callback指定返回data
  2. 如果我们指定callback,则返回由callback
  3. 如果callback指定 no 并且 1 不适用,则返回T

重载版本看起来像这样:

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){}
  }
}
于 2018-12-04T11:09:31.533 回答