The ideal solution to this in my opinion is to use Reselect
selectors (https://github.com/reactjs/reselect). Here is a contrived example:
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)
);
Used like this:
import { getObj } from './selectors';
const ExampleComponent = ({obj}) => <div>{ obj.name }</div>;
const mapStateToProps = state => ({
obj: getObj(state)
});
export default connect(mapStateToProps)(ExampleComponent);
The first time you run this, one obj
based on some id
(also in the state) will be "selected" from the list of all objs
. The next time, if the inputs have not changed (look at reselect documentation for the definition of equivalence) it will simply return the computed value from last time.
You also have the option to plug-in a different type of cache, e.g. LRU. That's a bit more advanced, but very doable.
The major advantage of Reselect is that it allows you to cleanly optimise without manually maintaining extra state in redux that you would then have to remember to update if a change to the original data was made. Timo's answer is good, but I would argue that the weakness is that it doesn't cache expensive client side computation (I know this wasn't the exact question, but this answer is about best practice redux caching in general, applied to your problem), only fetching. You can do something very similar to what Timo suggests, but incorporate reselect for a very tidy solution. In an action creator you could have something like this:
export const fetchObj = (dispatch, getState) => {
if (hasObj(getState())) {
return Promise.resolve();
}
return fetchSomething()
.then(data => {
dispatch(receiveObj(data));
return data;
});
};
You would have a selector specifically for hasObj
potentially based on the above selector (I do so here specifically to show how you can compose selectors easily), like:
export const hasObj = createSelector(
[ getObj ],
obj => !!obj
);
Once you start using this to interface with redux, it starts to make sense to use it exclusively in mapStateToProps
even for simple selects so that at a future time, if the way that state is computed changes, you do not need to modify all of the components which use that state. An example of this might be something like having an array of TODOs when is used to render a list in several different components. Later in your application development process you realise you want to filter that list of TODOs by default to only ones that are incomplete. You change the selector in one place and you are done.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…