簡體   English   中英

在redux-observable中編寫和排序多個史詩

[英]Composing and sequencing multiple epics in redux-observable

我有一個問題,我不知道如何解決。

我有兩個史詩要求api和更新商店:

const mapSuccess = actionType => response => ({
  type: actionType + SUCCESS,
  payload: response.response,
});

const mapFailure = actionType => error => Observable.of({
  type: actionType + FAILURE,
  error,
});

const characterEpic = (action$, store) =>
  action$.ofType(GET_CHARACTER)
    .mergeMap(({ id }) => {
      return ajax(api.fetchCharacter(id))
        .map(mapSuccess(GET_CHARACTER))
        .catch(mapFailure(GET_CHARACTER));
    });

const planetsEpic = (action$, store) =>
  action$.ofType(GET_PLANET)
    .mergeMap(({ id }) => {
      return ajax(api.fetchPlanet(id))
        .map(mapSuccess(GET_PLANET))
        .catch(mapFailure(GET_PLANET));
    });

現在我有一個簡單的場景,我想創建第三個結合上面兩個的動作,我們稱之為fetchCharacterAndPlanetEpic 我該怎么做? 我認為在很多情況下(以及在我的)中,第一個動作的結果在第二個動作開始之前被分派到商店是很重要的。 rxjsredux-observable redux-thunk ,這可能是微不足道的,但我無法想辦法用rxjsredux-observable來做到這rxjs

謝謝!

Tomasz的答案有效並且有利有弊(最初在redux-observable#33中提出 )。 一個潛在的問題是它使測試更難,但並非不可能。 例如,您可能必須使用依賴注入來注入分叉史詩的模擬。

在看到他之前我已經開始輸入一個答案了,所以我想我也可以把它發給后人,以防任何人都感興趣。

我之前也回答了另一個非常相似的問題,可能會有所幫助: 如何延遲一個史詩,直到另一個史詩發出一個值


我們可以發出getCharacter() ,然后在發出getPlanet()之前等待匹配的GET_CHARACTER_SUCCESS

const fetchCharacterAndPlanetEpic = (action$, store) =>
  action$.ofType(GET_CHARACTER_AND_PLANET)
    .mergeMap(({ characterId, planetId }) =>
      action$.ofType(GET_CHARACTER_SUCCESS)
        .filter(action => action.payload.id === characterId) // just in case
        .take(1)
        .mapTo(getPlanet(planetId))
        .startWith(getCharacter(characterId))
    );

這種方法的一個潛在的負面影響是理論上這個史詩接收的GET_CHARACTER_SUCCESS可能與我們正在等待的確切的不同。 過濾器action.payload.id === characterId檢查主要針對那個保護你,因為如果它具有相同的ID,那么它是否特別屬於你的並不重要。

要真正解決該問題,您需要某種獨特的事務跟蹤。 我個人使用自定義解決方案,該解決方案涉及使用幫助程序函數來包含唯一的事務ID。 像這樣的東西:

let transactionID = 0;

const pend = action => ({
  ...action,
  meta: {
    transaction: {
      type: BEGIN,
      id: `${++transactionID}`
    }
  }
});

const fulfill = (action, payload) => ({
  type: action.type + '_FULFILLED',
  payload,
  meta: {
    transaction: {
      type: COMMIT,
      id: action.meta.transaction.id
    }
  }
});

const selectTransaction = action => action.meta.transaction;

然后他們可以像這樣使用:

const getCharacter = id => pend({ type: GET_CHARACTER, id });

const characterEpic = (action$, store) =>
  action$.ofType(GET_CHARACTER)
    .mergeMap(action => {
      return ajax(api.fetchCharacter(action.id))
        .map(response => fulfill(action, payload))
        .catch(e => Observable.of(reject(action, e)));
    });

const fetchCharacterAndPlanetEpic = (action$, store) =>
  action$.ofType(GET_CHARACTER_AND_PLANET)
    .mergeMap(action =>
      action$.ofType(GET_CHARACTER_FULFILLED)
        .filter(responseAction => selectTransaction(action).id === selectTransaction(responseAction).id)
        .take(1)
        .mapTo(getPlanet(action.planetId))
        .startWith(getCharacter(action.characterId))
    );

關鍵細節是初始“掛起”操作在元對象中保存唯一的事務ID。 因此,初始操作基本上代表待處理的請求,然后在有人想要履行,拒絕或取消它時使用。 fulfill(action, payload)

我們的fetchCharacterAndPlanetEpic代碼有點冗長,如果我們使用這樣的東西,我們會做很多事情。 因此,讓我們為我們制作一個自定義操作員來處理它。

// Extend ActionsObservable so we can have our own custom operators.
// In rxjs v6 you won't need to do this as it uses "pipeable" aka "lettable"
// operators instead of using prototype-based methods.
// https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md
class MyCustomActionsObservable extends ActionsObservable {
  takeFulfilledTransaction(input) {
    return this
      .filter(output =>
        output.type === input.type + '_FULFILLED' &&
        output.meta.transaction.id === input.meta.transaction.id
      )
      .take(1);
  }
}
// Use our custom ActionsObservable
const adapter = {
  input: input$ => new MyCustomActionsObservable(input$),
  output: output$ => output$
};
const epicMiddleware = createEpicMiddleware(rootEpic, { adapter });

然后我們可以在史詩般的干凈利落中使用該自定義操作符

const fetchCharacterAndPlanetEpic = (action$, store) =>
  action$.ofType(GET_CHARACTER_AND_PLANET)
    .mergeMap(action =>
      action$.takeFulfilledTransaction(action)
        .mapTo(getPlanet(action.planetId))
        .startWith(getCharacter(action.characterId))
    );

這里描述的交易式解決方案是真正的實驗性的 在實踐中,我已經注意到了多年來的一些瑕疵,我還沒有考慮如何解決它們。 也就是說,總的來說,它在我的應用程序中非常有用。 實際上,它也可以用來做樂觀的更新和回滾! 幾年前,我將這個模式和可選的樂觀更新內容放入庫redux-transaction中,但我從來沒有回過頭來給它一些愛,所以使用風險自負 它應該被認為是被遺棄的,即使我可以回到它。

我在這個github主題中找到了幫助 首先,我必須創建幫助方法,這將允許我將史詩組合在一起:

import { ActionsObservable } from 'redux-observable';

const forkEpic = (epicFactory, store, ...actions) => {
  const actions$ = ActionsObservable.of(...actions);
  return epicFactory(actions$, store);
};

這允許我用諸如下面的存根動作來調用任何史詩:

const getCharacter = id => ({ type: GET_CHARACTER, id });
forkEpic(getCharacterEpic, store, getCharacter(characterId))

...並將返回結果可觀察到的史詩。 這樣我可以將兩個史詩結合在一起:

export const getCharacterAndPlanet = (characterId, planetId) => ({
  type: GET_CHARACTER_AND_PLANET,
  characterId,
  planetId,
});

const fetchCharacterAndPlanetEpic = (action$, store) =>
  action$.ofType(GET_CHARACTER_AND_PLANET)
    .mergeMap(({ characterId, planetId }) =>
      forkEpic(characterEpic, store, getCharacter(characterId))
        .mergeMap((action) => {
          if (action.type.endsWith(SUCCESS)) {
            return forkEpic(planetsEpic, store, getPlanet(planetId))
                     .startWith(action);
          }
          return Observable.of(action);
        })
    );

在此示例中,僅當第一個請求以SUCCESS結尾時才調用第二個請求。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM