如何使用 redux-observable 缓存 ajax 数据 - RxJS 5
How to cache ajax data with redux-observable - RxJS 5
我正在尝试缓存 ajax 通过 redux-observable 史诗调用的数据。
我的目标是仅在第一次发送 LOAD_DATA_REQUEST
时调用 API,然后在第二次 return 缓存数据时调用。
下面是我试过的代码,但是数据没有被缓存,每当我发送 LOAD_DATA_REQUEST
.
时都会调用 API
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)))
);
const getData$ = criteria => Observable.ajax.post('some-url', criteria)
.publishLast()
.refCount();
export default combineEpics(
loadDataEpic
);
我也试过这个:
const getData$ = criteria => Observable.ajax.post('some-url', criteria)
.publishReplay(1)
.refCount();
和
const getData$ = criteria => Observable.ajax.post('some-url', criteria)
.shareReplay();
首先,您需要一个 redux reducer,它将侦听来自 loadDataSuccess 的操作并将数据缓存在状态中。
其次,在你的史诗中过滤流并检查是否有任何数据处于状态。您可以使用 store.getState().
访问状态中的任何值
const loadDataEpic = (action$, store) =>
action$.ofType(LOAD_DATA_REQUEST)
.filter(() => !store.getState().yourDataCache)
.mergeMap(action => getData$(action.criteria)
.map(x => loadDataSuccess(x.response))
.catch(error => Observable.of(loadDataFailure(error.xhr)))
);
正如 ddcc3432 提到的,我个人会推迟将缓存结果存储在 redux 存储本身中。这是它最自然的地方。
这是一个一般的例子,假设您也保持某种加载状态。如果您不需要加载状态,那么在从缓存提供服务时分派一些操作不是必需的吗?您可以像 ddcc3432 提到的那样通过过滤器 LOAD_DATA_REQUEST
忽略。
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
)状态存储在其自己的 reducer 中更容易,因此您不需要将其与实际响应有效载荷进行额外合并——我个人这样做那个,但我在这个例子中没有,因为我大多数人都没有,它有时会把它们扔掉。
但是,您澄清说您希望改用 RxJS 重播,所以这是一种实现方式
(先看我对你的回答的评论)
如果您想基于 "criteria" 进行缓存,您可以创建自己的小帮手来执行此操作:
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
是在给定相同意图的情况下始终具有严格引用相等性的东西。如果它是一个对象,而你每次都创建一个新对象,它们将永远不会命中缓存,因为它们不相同 reference,它们具有不同的身份——无论它们是否具有相同的内容。
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。有时候因为各种原因无法逃避,但还是努力吧!正如您看到的缓存内容,它会让您的生活更轻松。
您可能需要考虑逐出缓存。当 window 打开时,此缓存是否始终无限期地保留结果?那不好吗?根据频率、大小等,这可能会导致严重的内存泄漏。小心 :)
我正在尝试缓存 ajax 通过 redux-observable 史诗调用的数据。
我的目标是仅在第一次发送 LOAD_DATA_REQUEST
时调用 API,然后在第二次 return 缓存数据时调用。
下面是我试过的代码,但是数据没有被缓存,每当我发送 LOAD_DATA_REQUEST
.
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)))
);
const getData$ = criteria => Observable.ajax.post('some-url', criteria)
.publishLast()
.refCount();
export default combineEpics(
loadDataEpic
);
我也试过这个:
const getData$ = criteria => Observable.ajax.post('some-url', criteria)
.publishReplay(1)
.refCount();
和
const getData$ = criteria => Observable.ajax.post('some-url', criteria)
.shareReplay();
首先,您需要一个 redux reducer,它将侦听来自 loadDataSuccess 的操作并将数据缓存在状态中。
其次,在你的史诗中过滤流并检查是否有任何数据处于状态。您可以使用 store.getState().
访问状态中的任何值const loadDataEpic = (action$, store) =>
action$.ofType(LOAD_DATA_REQUEST)
.filter(() => !store.getState().yourDataCache)
.mergeMap(action => getData$(action.criteria)
.map(x => loadDataSuccess(x.response))
.catch(error => Observable.of(loadDataFailure(error.xhr)))
);
正如 ddcc3432 提到的,我个人会推迟将缓存结果存储在 redux 存储本身中。这是它最自然的地方。
这是一个一般的例子,假设您也保持某种加载状态。如果您不需要加载状态,那么在从缓存提供服务时分派一些操作不是必需的吗?您可以像 ddcc3432 提到的那样通过过滤器 LOAD_DATA_REQUEST
忽略。
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
)状态存储在其自己的 reducer 中更容易,因此您不需要将其与实际响应有效载荷进行额外合并——我个人这样做那个,但我在这个例子中没有,因为我大多数人都没有,它有时会把它们扔掉。
但是,您澄清说您希望改用 RxJS 重播,所以这是一种实现方式
(先看我对你的回答的评论)
如果您想基于 "criteria" 进行缓存,您可以创建自己的小帮手来执行此操作:
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
是在给定相同意图的情况下始终具有严格引用相等性的东西。如果它是一个对象,而你每次都创建一个新对象,它们将永远不会命中缓存,因为它们不相同 reference,它们具有不同的身份——无论它们是否具有相同的内容。
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。有时候因为各种原因无法逃避,但还是努力吧!正如您看到的缓存内容,它会让您的生活更轻松。
您可能需要考虑逐出缓存。当 window 打开时,此缓存是否始终无限期地保留结果?那不好吗?根据频率、大小等,这可能会导致严重的内存泄漏。小心 :)