编写自定义 ngrx 运算符并返回源可观察类型

Writing a custom ngrx operator and returning the source observable type

我有一个自定义运算符,waitFor,我在我的效果中使用它:

public effect$: Observable<Action> = createEffect(() => {
    return this.actions$.pipe(
      ofType(myAction),
      waitFor<ReturnType<typeof myAction>>([anotherAction]),
      ...etc
    );
  });

它基本上会查看 correlationId,以便在分派操作数组之前不继续执行。但这不是重点。

正如预期的那样,ofType 采用源可观察并将其用作 return 类型,但是我正在努力实现相同的效果。正如您在上面看到的,我在我的 waitFor 方法中使用 ReturnType<typeof myAction>> 和以下内容:

export function waitFor<A extends Action>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {

所以现在如果我这样调用 waitFor:

public effect$: Observable<Action> = createEffect(() => {
    return this.actions$.pipe(
      ofType(myAction),
      waitFor([anotherAction]),
      ...etc
    );
  });

然后它的类型被推断为Action,但我希望这个默认为ReturnType<typeof theSourceObservable>。所以我假设我的 waitFor 方法中需要这样的东西:

export function waitFor<A extends ReturnType<typeof sourceObservable?!>>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {

waitFor 看起来像这样:

export function waitFor<A extends Action>(actionsToWaitFor$: Array<Actions>): OperatorFunction<A, A> {
  return (source$) => {
    return source$.pipe(
      switchMap((action: A & { correlationId: string}) => {
        // use zip() to wait for all actions 
        // and when omitting map((action) => action)
        // so the original action is always returned
      })
    );
  };
}

ofTypesource看来我需要用Extract

更新

显示了 StackBlitz 示例 here

这至少可以编译;不知道是不是也满足你的需求

public effect3$: Observable<Action> = createEffect(() => {
  const a:Action[]= []

  return this.actions$.pipe(
    ofType(doSomething),
    this.someCustomOperatorReturningStaticTypes(),
    this.thisWontWork(a),
    tap(({aCustomProperty}) => {
      // The type is inferred
      console.log(aCustomProperty);
    }),
  )
});

private thisWontWork<A extends Action>(actionsToWaitFor$: Action[]): OperatorFunction<A, A> {
  return (source$) => {
    return source$.pipe(
      tap(() => {
        console.log('Should work')
      })
    )
  }
}

我无法在 StackBlitz 中运行它,有什么提示吗?

希望对您有所帮助

遇到这个问题,我想我会添加一些解释,以防你仍然想知道,如果以后有人遇到这个问题!

首先 - 在您的示例中,您在 return 类型方面想得太多了。

如果您想根据传递的类型推断某些东西(例如,在这里您希望您的操作按其类型 returned),您只需 return 相同的泛型即可你通过了——正如答案所说:

function foo<A>(val: A): A {
  return val;
}

如果我用一个数字调用 foo(),我会得到一个数字。如果我用特定的动作类型调用它,那么会发生同样的事情。这就是为什么你只需要 return type OperatorFunction<A, A> - 同样的东西进来,同样的东西出来。

其次 - ofType 是如何工作的?

这里的关键是您需要一些正在缩小范围的操作。

使用 createAction 可以为您解决这个问题,但为了举例,我将手动创建它。

type FooAction = { type: 'FOO_ACTION' };
type BarAction = { type: 'BAR_ACTION' };

// This type gets built by you using createAction - this is your Action type
type MyActions = FooAction | BarAction;

接下来我们需要一个类型来执行我们的操作集 (MyActions) - 并根据类型将其缩小到特定操作。

type NarrowActionType<T extends { type: any }, K extends string> = T extends { type: K }
  ? T
  : never;

我们可以验证这是否有效:

type NarrowedActionTest = NarrowActionType<MyActions, 'FOO_ACTION'>;

ofType 是一个高阶函数,它首先接受一个字符串,然后是要操作的可观察流。我不会使用 observables 来让它更简单,但原理是一样的。

本质上,我们想要类似于 (type: string) => (action: Action) => action is NarrowActionType 的类型签名。

  • NarrowActionType 需要整体操作类型作为第一个类型参数,我们将其称为 A
  • NarrowActionType 需要我们用来缩小的字符串文字类型,我们称之为 T
  • T 将是一个字符串,所以 T 扩展字符串
  • 我们一开始只知道 T 的类型,然后在应用对象流后知道 A 的类型,所以让我们在每个函数应用程序中使用一个通用参数
  • 该函数将最终检查 A.type 是否与类型相同。所以我们应该强制这个存在,A extends { type: any }
function isOfType<T extends string>(type: T) {
    return <A extends { type: any }>(action: A): action is NarrowActionType<A, T> => {
        return action.type === type
    }
}

现在我们可以在 MyActions 类型上测试这个函数,看看它是否可以缩小输出:


const myAction: MyActions = { type: 'FOO_ACTION' };
const isFooAction = isOfType('FOO_ACTION')(myAction);

if (isOfType('FOO_ACTION')(myAction)) {
    type myNarrowedAction = typeof myAction; // is FooAction
}

完整游乐场 link 此处: https://www.typescriptlang.org/play/#code/C4TwDgpgBAcghgJwQewO4EEDGwCWyB2AKuBADyFQQAewE+AJgM5QDeUokAXFHPiFAF8ANFADSlGnSZRGwBDnwBzAHxQAvFArVaDZmw4Ru4gQCgoUAPyazUbvggA3CAgDcJkwagAxZMiy4CdVZ2Em4Aci8AeUiAfXQAYUIASUiYMME3TwAhRH88fCD9UKgwrPQAJTjElLSM9xMAegbNAAscZk9FCGBmACMAVxwAG2AoXv4QZH6ofsYFRShMBAg4WjzAgFp2NuZ2qEn+hCh1goMPEigAWRAT5g0fP2x8qAAfKByEE7dzyFhEFFQEHoJ0IEFkQXgSDQIJIpGutxEEWiVWSqTCym+ADN+vgnoF2pFMcRIOQJDppLJ5EplAAKAzcQgASlYNnMy2AhwKpHQZKkehCXB4fEEtLgePw3HQjO4YoCBT2kIBMJJ6BEhFUalULFZ5l17M5PHFADpPGozQKIDrTKYTJgCOCALY3cXceHiu7BeklKKxBKo2oCNx2-Dg9oPE5BAlEkg0pG+6poxk0p0nRnfHCYqA0qPEiCxn0omphJMp8WM5na3UWqBOxVoIERjQGZCZ0tylxQJpQPbh8UmUxAA