0

我正在使用 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 一起使用将产生错误而没有任何自动保存。我没有返回一个 thunk,而是将其更改为:

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

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

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

4

2 回答 2

3

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

目前尚不清楚updateFretboard()返回什么,除非那可能不是 POJO 操作;这是任何fretboards.child(firebaseKey).update(fretboardData)回报。

如果不是发出一个动作,而是实际上只是将其作为副作用执行,但完全忽略它的返回值,那么您将使用类似do()operator的东西,它用于在不实际修改下一个值的情况下产生副作用。然后,您可以将其与ignoreElements()运算符结合起来,以防止您的史诗永远发射任何东西。

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(),您的史诗实际上会重新发出它匹配的相同动作,从而导致不必要的递归。


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

例如

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

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

于 2017-05-17T05:04:16.800 回答
1

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

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

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

借用 Jay 在另一篇文章中给出的另一个答案(使用 fetch 而不是 ajax 和 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 实现的,因此我真的看不出有什么办法。目前,我想不出我想取消自动保存的原因,所以我对这个解决方案很满意!

于 2017-05-17T18:26:46.523 回答