如何使用 redux-observable 在一个史诗中发出多个动作?

How to emit multiple actions in one epic using redux-observable?

我是 rxjs/redux observable 的新手,想做两件事:

1) 改进这部史诗,使其更加地道

2) 从单个史诗调度两个动作

我看到的大多数示例都假设 api 库会在获取失败时抛出异常,但我将我的设计设计得更可预测,并且使用 Typescript 联合类型我在解压结果之前被迫检查 ok: boolean 值,因此理解如何在 rxjs 中执行此操作更具挑战性。

改进以下内容的最佳方法是什么?如果请求成功,我想同时发出成功操作(意味着用户已获得授权)和 'fetch account' 操作,这是一个单独的操作,因为有时我可能需要获取帐户在 'logging in' 之外。任何帮助将不胜感激!

const authorizeEpic: Epic<ActionTypes, ActionTypes, RootState> = action$ =>
  action$.pipe(
    filter(isActionOf(actions.attemptLogin.request)),
    switchMap(async val => {
      if (!val.payload) {
        try {
          const token: Auth.Token = JSON.parse(
            localStorage.getItem(LOCAL_STORAGE_KEY) || ""
          );
          if (!token) {
            throw new Error();
          }
          return actions.attemptLogin.success({
            token
          });
        } catch (e) {
          return actions.attemptLogin.failure({
            error: {
              title: "Unable to decode JWT"
            }
          });
        }
      }

      const resp = await Auth.passwordGrant(
        {
          email: val.payload.email,
          password: val.payload.password,
          totp_passcode: ""
        },
        {
          url: "localhost:8088",
          noVersion: true,
          useHttp: true
        }
      );

      if (resp.ok) {
        return [
          actions.attemptLogin.success({
            token: resp.value
          })
          // EMIT action 2, etc...
        ];
      }

      return actions.attemptLogin.failure(resp);
    })
  );

您可以压缩您的操作并 return 它们。

zip(actions.attemptLogin.success({
            token: resp.value
          })
          // EMIT action 2, etc...

因此,现在您的两个操作都将被调用。

docs for switchMap 表示项目函数(您示例中的 lambda)可能 return 如下:

type ObservableInput<T> = SubscribableOrPromise<T> | ArrayLike<T> | Iterable<T>

Promise<T> 被 returned 时,简单地发出解析值。在您的示例中,如果您 return 来自 async 范围的数组,则 array 将直接发送到 Redux 存储。假设您没有特殊的 Redux 中间件设置来处理一系列事件,这可能不是您想要的。相反,我建议 return 在项目函数中设置一个可观察对象。这是对您的示例的轻微修改:

const authorizeEpic: Epic<ActionTypes, ActionTypes, RootState> = action$ =>
  action$.pipe(
    filter(isActionOf(actions.attemptLogin.request)), // or `ofType` from 'redux-observable'?
    switchMap(action => {
      if (action.payload) {
        try {
          const token: Auth.Token = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || "")
          if (!token) {
            throw new Error()
          }

          // return an observable that emits a single action...
          return of(actions.attemptLogin.success({
            token
          }))
        } catch (e) {
          // return an observable that emits a single action...
          return of(actions.attemptLogin.failure({
            error: {
              title: "Unable to decode JWT"
            }
          }))
        }
      }

      // return an observable that eventually emits one or more actions...
      return from(Auth.passwordGrant(
        {
          email: val.payload.email,
          password: val.payload.password,
          totp_passcode: ""
        },
        {
          url: "localhost:8088",
          noVersion: true,
          useHttp: true
        }
      )).pipe(
        mergeMap(response => response.ok
          ? of(
            actions.attemptLogin.success({ token: resp.value }),
            // action 2, etc...
          )
          : of(actions.attemptLogin.failure(resp))
        ),
      )
    }),
  )

我没有你的 TypeScript 类型定义,所以我无法验证上面的例子是否正确。但是,我在较新版本的 TypeScript、RxJS 和 redux-observable 上取得了相当大的成功。上面没有任何突出的地方让我认为你应该遇到任何问题。