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

Implementing Auto-Save in React-Redux-Firebase App using Redux-Observable

我正在使用 redux-observable 为 react-redux-firebase 副项目实现自动保存。目前我有一个 updateFretboardEpic 响应任何修改当前拥有对 Firebase 数据库的引用 (firebaseKey) 的 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 一起使用将产生错误而不会自动保存。而不是 returning thunk,我将其更改为:

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

有趣的是,updateFretboardEpic 将自动保存流中的第一个操作,return 错误,并且不会自动保存此后的任何后续操作。 updateFretboard 目前不流经我的任何减速器(它只负责将新状态传递给 Firebase),尽管我将来可能会选择接收一个承诺,知道保存发生的时间并通过我的减速器传递它。

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

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

不清楚 updateFretboard() return 是什么,除了它可能不是 POJO 操作;随便 fretboards.child(firebaseKey).update(fretboardData) returns.

如果不是发出一个动作,你实际上只是想把它作为副作用执行但完全忽略它的 return 值,你会使用类似 do() operator, which is used to make a side effect without actually modifying the next'd values. You could then combine that with the 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() 这个特定的史诗将 永远不会 发出任何东西(尽管它仍然会传播 errors/complete)。它基本上变成了 "readonly".

如果您不使用 ignoreElements(),您的史诗实际上会 re-emit 它匹配的相同动作,导致不需要的递归。


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

例如

// 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
});

相应地根据您的应用需求调整惯例。

我找到了解决问题的方法(感谢 Jay 为我指明了正确的方向!)。

在我的问题中,我没有明确说明 fretboards.child(firebaseKey).update(fretboardData) 到底是什么。 fretboards.child(firebaseKey) 是对特定子项的引用 具有指定 firebase 键的指板(完整路径为 firebase.database().ref('fretboards').child(firebaseKey))。 Firebase 数据库使用基于 Promise 的 API,因此更新将 return 一个 Promise:

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

借用 Jay 给出的不同 post () 的另一个答案,我想出了以下解决方案:

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 }
      ))
    );
};

根据自动保存承诺是否成功,将 return 一个布尔值来指示保存成功,它映射到我的新操作 AUTOSAVE_FRETBOARD可以传递给我的减速器。

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