使用 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 实现的,我真的没有办法解决这个问题。目前,我想不出取消自动保存的原因,所以我对这个解决方案很满意!
我正在使用 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 实现的,我真的没有办法解决这个问题。目前,我想不出取消自动保存的原因,所以我对这个解决方案很满意!