使用Redux-Observable在React-Redux-Firebase应用程序中实现自动保存

时间:2017-05-17 04:43:21

标签: firebase react-redux rxjs5 redux-thunk redux-observable

我正在使用redux-observable为react-redux-firebase端项目实现自动保存。目前我有一个updateFretboardEpic,它响应任何修改当前拥有Firebase数据库引用(frebaseKey)的Fretboard组件的操作。消除1秒后,updateFretboard应将组件的新状态保存到Firebase。

import {
  ADD_STRING,
  REMOVE_STRING,
  INC_STR_PITCH,
  DEC_STR_PITCH,
  ADD_FRET,
  REMOVE_FRET,
  UPDATE_FRETBOARD
} from '../actions/action-types';

import {
  updateFretboard
} from '../actions/fretboard-actions';

export const updateFretboardEpic = (action$, store) => {
  return action$.ofType(
      ADD_STRING,
      REMOVE_STRING,  
      INC_STR_PITCH,
      DEC_STR_PITCH,
      ADD_FRET,
      REMOVE_FRET
    )
    .filter(() => store.getState().fretboard.firebaseKey !== undefined)
    .debounceTime(1000)
    .map(() => updateFretboard(
      store.getState().fretboard.fretboardData,
      store.getState().fretboard.firebaseKey
    ));
};

但是,我目前收到以下错误:

Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
    at Object.performAction (<anonymous>:3:2312)
    at liftAction (<anonymous>:2:27846)
    at dispatch (<anonymous>:2:31884)
    at bundle.ff2509b….js:9896
    at SafeSubscriber.dispatch [as _next] (vendor.ff2509b….js:51341)
    at SafeSubscriber.__tryOrUnsub (bundle.ff2509b….js:392)
    at SafeSubscriber.next (bundle.ff2509b….js:339)
    at Subscriber._next (bundle.ff2509b….js:279)
    at Subscriber.next (bundle.ff2509b….js:243)
    at SwitchMapSubscriber.notifyNext (bundle.ff2509b….js:6579)

在实现redux-observable之前,updateFretboard使用Redux-Thunk来发送一个动作:

export function updateFretboard(fretboardData, firebaseKey = undefined) {
  return dispatch => { fretboards.child(firebaseKey).update(fretboardData); };
}

使用它与redux-observable一起使用将产生错误而不进行任何自动保存。而不是返回一个thunk,我把它改为:

export function updateFretboard(fretboardData, firebaseKey = undefined) {
  return fretboards.child(firebaseKey).update(fretboardData);
}

有趣的是,updateFretboardEpic将自动保存流中的第一个操作,返回错误,并且不会为此后的任何后续操作自动保存。 updateFretboard当前没有流经我的任何Reducer(它只负责将新状态传递给Firebase),尽管我可能会选择在将来接收保证何时发生保存并通过我的Reducer传递它的承诺。

我是RxJS / redux-observable的新手,所以我怀疑有更好的方法可以做到这一点。思考?

2 个答案:

答案 0 :(得分:3)

当使用redux-observable而没有任何其他副作用时,中间件(如redux-thunk)意味着您发送的所有操作必须是普通的旧JavaScript对象 - 包括您的史诗发出的任何内容。

不清楚updateFretboard()返回什么,除了可能不是POJO动作;它是fretboards.child(firebaseKey).update(fretboardData)返回的任何内容。

如果不是发出动作,你实际上只是将其作为副作用而是完全忽略它的返回值,你会使用类似do() operator的东西,它用于产生副作用而不实际修改下一个值。然后你可以将它与ignoreElements() operator结合起来,以防止你的史诗发出任何东西。

export const updateFretboardEpic = (action$, store) => {
  return action$.ofType(
      ADD_STRING,
      REMOVE_STRING,  
      INC_STR_PITCH,
      DEC_STR_PITCH,
      ADD_FRET,
      REMOVE_FRET
    )
    .filter(() => store.getState().fretboard.firebaseKey !== undefined)
    .debounceTime(1000)
    .do(() => updateFretboard(
      store.getState().fretboard.fretboardData,
      store.getState().fretboard.firebaseKey
    ))
    .ignoreElements();
};

请记住,通过使用ignoreElements()这个特殊的史诗永远不会发出任何东西(虽然它仍会传播错误/完成)。它基本上变成了“只读”。

如果你没有使用ignoreElements(),那么你的史诗会重新发出它匹配的相同动作,导致不必要的递归。

您可能还会发现更容易反转对保存内容和不保存内容的控制。您不必维护应该触发自动保存的操作列表,而是可以让操作具有某种属性,史诗会听取这些属性以便保存。

e.g。

// Listens for actions with `autosave: true`
export const updateFretboardEpic = (action$, store) => {
  return action$.filter(action => action.autosave)
    // etc...
};

// e.g.
store.dispatch({
  type: ADD_STRING,
  autosave: true
});

根据您的应用需求调整约定。

答案 1 :(得分:1)

我找到了解决问题的方法(感谢周杰伦指出我正确的方向!)。

在我的问题中,我没有明确说明fretboards.child(firebaseKey).update(fretboardData)实际上是什么。 fretboards.child(firebaseKey)是对特定孩子的引用 具有指定firebase密钥的指板(完整路径为firebase.database()。ref('fretboards')。child(firebaseKey))。 Firebase数据库使用基于Promise的API,因此更新将返回Promise:

const updateRequest=fretboards.child(firebaseKey).update(fretboardData)
  .then(function(result) {
    // do something with result
  }, function(err) {
    // handle error
  });

借用Jay给出的另一个帖子(Use fetch instead of ajax with redux-observable)的另一个答案,我提出了以下解决方案:

import { fretboards } from '../firebase-config.js';
import Rx from 'rxjs/Rx'; 

import {
  ADD_STRING,
  REMOVE_STRING,
  INC_STR_PITCH,
  DEC_STR_PITCH,
  ADD_FRET,
  REMOVE_FRET,
  AUTOSAVE_FRETBOARD
} from '../actions/action-types';

function autoSaveFretboard(fretboardData, firebaseKey = undefined) {
  let autoSaveFretboardRequest = fretboards.child(firebaseKey)
    .update(fretboardData).then(function(result) {
      // won't need to pass result through reducers, so return
      // true to indicate save was successful
      return true;
    }, function(err) {
      // log error and return false in case of autosave failure
      console.log(err);
      return false;
    });
  return Rx.Observable.from(autoSaveFretboardRequest);
}

export const autoSaveFretboardEpic = (action$, store) => {
  return action$.ofType(
      ADD_STRING,
      REMOVE_STRING,  
      INC_STR_PITCH,
      DEC_STR_PITCH,
      ADD_FRET,
      REMOVE_FRET
    )
    .filter(() => store.getState().fretboard.firebaseKey !== undefined)
    .debounceTime(750)
    .mergeMap(() => autoSaveFretboard(
        store.getState().fretboard.fretboardData,
        store.getState().fretboard.firebaseKey
      ).map((res) => (
        { type: AUTOSAVE_FRETBOARD, success: res }
      ))
    );
};

根据自动保存承诺是否成功,将返回一个布尔值,表示保存成功,该行映射到一个新的动作AUTOSAVE_FRETBOARD,我可以传递给我的减速器。

正如杰伊在之前的帖子中指出的那样,承诺无法取消。由于Firebase数据库是作为基于Promise的API实现的,所以我真的没有办法解决这个问题。目前,我想不出有什么理由要取消自动保存,所以我很满意这个解决方案!