正如 ddcc3432 所提到的,我个人会推迟将缓存的结果存储在 redux 存储本身中。这是它最自然的地方。
这是一个这样做的一般示例,假设您也保持某种加载状态。如果您不需要加载状态,那么在从缓存提供服务时调度一些操作不是必需的吗?您可以通过过滤器忽略LOAD_DATA_REQUEST
ddcc3432 提到的。
const yourDataCache = (state, action) => {
switch (action.type) {
case LOAD_DATA_REQUEST:
return {
...state,
// however you index your requests
[action.criteria]: {
isLoading: true
}
};
case LOAD_DATA_SUCCESS:
return {
...state,
// however you index your responses
// here I'm assuming criteria is a string and is
// also included in the response. Change as needed
// for your real data
[action.criteria]: {
isLoading: false,
...action.response
}
};
case LOAD_DATA_CACHED:
return {
...state,
// however you index your responses
[action.criteria]: {
isLoading: false, // just change the loading state
...state[action.criteria] // keep existing cache!
}
};
default:
return state;
}
};
const loadDataEpic = (action$, store) =>
action$.ofType(LOAD_DATA_REQUEST)
.mergeMap(action => {
const { yourDataCache } = store.getState();
// If the data is already cached, we don't need to
// handle errors. All this code assumes criteria is
// a simple string!
if (yourDataCache[action.criteria]) {
return Observable.of({
type: LOAD_DATA_CACHED,
criteria: action.criteria
});
} else {
return getData(action.criteria)
.map(x => loadDataSuccess(x.response))
.catch(error => Observable.of(loadDataFailure(error.xhr)))
}
});
您可能会发现将加载(例如)状态存储在它自己的减速器中更容易isLoading
,因此您不需要将它与实际响应有效负载进行额外的合并——我个人这样做了,但在这个例子中我没有因为我大多数都没有,它有时会把它们扔掉。
但是,您澄清说您希望使用 RxJS 重播,所以这是一种方法
(先看我对你的回答的评论)
如果您想根据“标准”进行缓存,您可以创建自己的小助手来执行此操作:
const cache = new Map();
const getData = criteria => {
if (cache.has(criteria)) {
return cache.get(criteria);
} else {
// Using publishReplay and refCount so that it keeps the results
// cached and ready to emit when someone subscribes again later
const data$ = Observable.ajax.post('some-url', criteria)
.publishReplay(1)
.refCount();
// Store the resulting Observable in our cache
// IMPORTANT: `criteria` needs to be something that will later
// have reference equallity. e.g. a string
// If its an object and you create a new version of that object every time
// then the cache will never get a hit, since cache.has(criteria) will return
// false for objects with different identities. `{ foo: 1 } !== { foo: 1}`
cache.set(criteria, data$);
return data$;
}
};
const loadDataEpic = action$ =>
action$.ofType(LOAD_DATA_REQUEST)
.mergeMap(action =>
getData(action.criteria)
.map(x => loadDataSuccess(x.response))
.catch(error => Observable.of(
loadDataFailure(error.xhr)
))
);
但是,至关重要的criteria
是,在相同意图的情况下,始终具有严格的引用相等性。如果它是一个对象并且您每次都创建一个新对象,那么它们将永远不会获得缓存命中,因为它们不是相同的引用,它们具有不同的身份——无论它们是否具有相同的内容。
let a = { foo: 1 };
let b = { foo: 1 };
a === b;
// false, because they are not the same object!
如果您需要使用对象并且不能以其他方式关闭某些原语(如 ID 字符串),您将需要一些方法来序列化它们。
JSON.stringify({ foo: 1, bar: 2 }) === JSON.stringify({ foo: 1, bar: 2 })
// true, but only if the keys were defined in the same order!!
JSON.stringify({ bar: 2, foo: 1 }) === JSON.stringify({ foo: 1, bar: 2 })
// false, in most browsers JSON.stringify is not "stable" so because
// the keys are defined in a different order, they serialize differently
// See https://github.com/substack/json-stable-stringify
尽量使用唯一的 ID 并将其发送到服务器,而不是复杂的 JSON。有时因为各种原因无法避免,但要努力!正如您在缓存中看到的那样,它会让您的生活更轻松。
您可能需要考虑缓存驱逐。当窗口打开时,这个缓存是否总是无限期地保留结果?那不好吗?根据频率、大小等,这可能会导致严重的内存泄漏。小心:)