我正在使用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的新手,所以我怀疑有更好的方法可以做到这一点。思考?
答案 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实现的,所以我真的没有办法解决这个问题。目前,我想不出有什么理由要取消自动保存,所以我很满意这个解决方案!