如果我的商店中已有数据,我想避免两次调用API。
如何使用Redux执行此操作?
答案 0 :(得分:9)
我假设您使用async actions来处理API调用。
这是我放置缓存逻辑的地方,这会产生一个很好的封装:
export function fetchData(url) {
return function (dispatch) {
dispatch(requestData(url))
if (isCached(url)) {
const cachedData = getCached(url)
dispatch(receiveData(url, cachedData))
} else {
return fetch(url)
.then(response => response.json())
.then(json => {
dispatch(receiveData(url, json))
setCached(url, json)
})
}
}
}
为本地存储实施isCached
,getCached
和setCached
应该相当简单。
答案 1 :(得分:8)
我认为理想的解决方案是使用Reselect
选择器(https://github.com/reactjs/reselect)。这是一个人为的例子:
import { createSelector } from 'reselect';
const getObjs = state => state.objs;
const currentObjId = state => state.currentObjId;
export const getObj = createSelector(
[ currentObjId, getObjs ],
(id, objs) => objs.get(href)
);
像这样使用:
import { getObj } from './selectors';
const ExampleComponent = ({obj}) => <div>{ obj.name }</div>;
const mapStateToProps = state => ({
obj: getObj(state)
});
export default connect(mapStateToProps)(ExampleComponent);
首次运行时,将从所有obj
的列表中“选择”基于某些id
(也在州内)的一个objs
。下一次,如果输入没有改变(查看等效定义的重新选择文档),它将只返回上次计算的值。
您还可以选择插入不同类型的缓存,例如LRU。这有点先进,但非常可行。
Reselect的主要优点是它允许您干净地优化而无需手动维护redux中的额外状态,如果对原始数据进行了更改,则必须记住更新。蒂莫的答案很好,但我认为它的缺点是它不会缓存昂贵的客户端计算(我知道这不是确切的问题,但这个答案是关于最佳实践的redux缓存,一般适用于你的问题),只取。你可以做一些与蒂莫建议的非常类似的事情,但是将重新选择纳入一个非常整洁的解决方案。在动作创建者中你可以有这样的东西:
export const fetchObj = (dispatch, getState) => {
if (hasObj(getState())) {
return Promise.resolve();
}
return fetchSomething()
.then(data => {
dispatch(receiveObj(data));
return data;
});
};
你可能有一个专门用于hasObj
的选择器,可能基于上面的选择器(我这里专门用来展示如何轻松地组合选择器),如:
export const hasObj = createSelector(
[ getObj ],
obj => !!obj
);
一旦你开始使用它与redux接口,即使对于简单的选择,在mapStateToProps
中专门使用它也是有意义的,以便在将来的时间,如果计算状态的方式发生变化,你会做不需要修改使用该状态的组件的所有。这种情况的一个例子可能是,当用于在几个不同的组件中呈现列表时,有一个TODO数组。稍后在您的应用程序开发过程中,您意识到您希望默认情况下仅将那些TODO列表过滤为不完整的。您可以在一个地方更改选择器,然后就完成了。
答案 2 :(得分:4)
我提出了同样的问题,我想在我的动作和reducer之间添加一个缓存层。我的解决方案是创建一个中间件,在请求操作进入实际的thunk之前缓存它,该操作从API请求数据。
这样做有一个优点,您不需要修改现有的操作和减速器。你只需添加一个中间件。以下是中间件的外观:
const cache = store => next => action => {
// handle FETCH action only
if (action.type !== 'FETCH') {
return next(action);
}
// check if cache is available
const data = window['__data'];
if (!data) {
// forward the call to live middleware
return next(action);
}
return store.dispatch({ type: 'RECEIVE', payload: { data: `${data} (from cache)` } });
}
export default cache;
在https://stackblitz.com/edit/redux-cache-middleware试用现场演示或查看我的博文,了解更多信息http://www.tonnguyen.com/2018/02/13/web-programming/implement-a-cache-middleware-for-redux/
答案 3 :(得分:2)
一种简单快捷的方法(虽然不推荐用于任何可扩展的):
使用redux-persist来保留(缓存)商店。每当它重新水化时,您就知道您之前存在的数据 - 请阅读链接中的文档,了解它的工作原理以及如何设置。
为避免远程服务器上出现不必要的数据提取,您可以将URL(as Timos answer)缓存到localStorage等,并在进行实际提取之前检查其是否存在。
动作:
function fetchUsers(url){
if(isCached(url)) {
// The data is already in the store, thanks to redux-persist.
// You can select it in the state as usual.
dispatch({ type: 'IS_CACHED', payload: url })
} else {
return fetch(url)
.json()
.then((response) => {
dispatch({ type: 'USERS_FETCH_SUCCESS', payload: response.data })
setCached(url)
})
.catch((error) => {
dispatch({ type: 'USERS_FETCH_FAILED', payload: error })
})
}
}
网址的简单自定义缓存:
const CACHE_NAME = 'MY_URL_CACHE'
export function getUrlCache() {
var localStorage
try {
localStorage = window.localStorage
// Get the url cache from the localstorage, which is an array
return ( localStorage.getItem(CACHE_NAME) ? JSON.parse(localStorage.getItem(CACHE_NAME)) : [] )
} catch(e){
// Make your own then...
throw "localStorage does not exist yo"
}
}
export function isCached(url) {
var urlCache = getUrlCache()
return urlCache.indexOf(url) !== -1
}
export function setCached(url){
if( isCached(url) )
return false
var urlCache = getUrlCache()
urlCache.push(url)
localStorage.setItem(CACHE_NAME, urlCache)
return true
}
export function removeCached(url){
var myCache = getUrlCache()
var index = myCache.indexOf(url)
if(index !== -1){
myCache = myCache.splice(index, 1)
localStorage.setItem(CACHE_NAME, urlCache)
return true
} else {
return false
}
}
当/如果刷新了redux-persist数据或者使数据“变旧”的其他东西时,您还需要删除缓存的URL。
我建议使用带有持久性的redux存储来完成整个事情,而是在其上建立reducer / action逻辑。有很多方法可以做到这一点,我强烈建议您探索redux,redux-saga和redux-persist以及常见的概念/设计模式。
基本示例的旁注: 您还可以使用redux-persist-transform-expire transform for redux-persist让缓存的数据在某个时间点到期,并修改它以删除相关的缓存URL。
答案 4 :(得分:0)
我为此专门建立了一个库-redux-cached-api-middleware。
一个示例用法,成功的响应将被缓存(从商店中重复使用)10分钟:
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import api from 'redux-cached-api-middleware';
import Items from './Items';
import Error from './Error';
class ExampleApp extends React.Component {
componentDidMount() {
this.props.fetchData();
}
render() {
const { result } = this.props;
if (!result) return null;
if (result.fetching) return <div>Loading...</div>;
if (result.error) return <Error data={result.payload} />;
if (result.payload) return <Items data={result.payload} />;
return <div>No items</div>;
}
}
ExampleApp.propTypes = {
fetchData: PropTypes.func.isRequired,
result: PropTypes.shape({}),
};
const enhance = connect(
state => ({
result: api.selectors.getResult(state, 'GET/my-api.com/items'),
}),
dispatch => ({
fetchData() {
return dispatch(
api.actions.invoke({
method: 'GET',
headers: { Accept: 'application/json' },
endpoint: 'https://my-api.com/items/',
cache: {
key: 'GET/my-api.com/items',
strategy: api.cache
.get(api.constants.CACHE_TYPES.TTL_SUCCESS)
.buildStrategy({ ttl: 10 * 60 * 1000 }), // 10 minutes
},
})
);
},
})
);
export default enhance(ExampleApp);
您可以传递缓存strategy
或自定义shouldFetch
函数来确定何时应重新获取资源(docs)。
该库使用redux-thunk
(用于异步操作)和redux-api-middleware
(用于调用API)作为对等方依赖项,并且设置非常简单:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { apiMiddleware } from 'redux-api-middleware';
import api from 'redux-cached-api-middleware';
import reducers from './reducers';
const store = createStore(
combineReducers({
...reducers,
[api.constants.NAME]: api.reducer,
}),
applyMiddleware(thunk, apiMiddleware)
);
答案 5 :(得分:0)
不要重新发明缓存,只需利用HTTP缓存即可。
您的代码实际上应该不了解缓存机制。
只需在需要数据时发出http请求,无论是通过redux thunk,promise等,还是直接不带redux都没关系。
HTTP缓存将为您进行缓存。
当然,要使其正常工作,您需要正确配置服务器以设置适当的缓存参数和有效性。