I want to persist some parts of my state tree to the localStorage. What is the appropriate place to do so? Reducer or action?
问题:
回答1:
Reducer is never an appropriate place to do this because reducers should be pure and have no side effects.
I would recommend just doing it in a subscriber:
store.subscribe(() => {
// persist your state
})
Before creating the store, read those persisted parts:
const persistedState = // ...
const store = createStore(reducer, persistedState)
If you use combineReducers()
you’ll notice that reducers that haven’t received the state will “boot up” as normal using their default state
argument value. This can be pretty handy.
It is advisable that you debounce your subscriber so you don’t write to localStorage too fast, or you’ll have performance problems.
Finally, you can create a middleware that encapsulates that as an alternative, but I’d start with a subscriber because it’s a simpler solution and does the job well.
回答2:
To fill in the blanks of Dan Abramov's answer you could use store.subscribe()
like this:
store.subscribe(()=>{
localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})
Before creating the store, check localStorage
and parse any JSON under your key like this:
const persistedState = localStorage.getItem('reduxState') ? JSON.parse(localStorage.getItem('reduxState')) : {}
You then pass this peristedState
constant to your createStore
method like this:
const store = createStore(
reducer,
persistedState,
/* any middleware... */
)
回答3:
In a word: middleware.
Check out redux-persist. Or write your own.
[UPDATE 18 Dec 2016] Edited to remove mention of two similar projects now inactive or deprecated.
回答4:
If anybody is having any problem with the above solutions, you can write your own to. Let me show you what I did. Ignore saga middleware
things just focus on two things localStorageMiddleware
and reHydrateStore
method. the localStorageMiddleware
pull all the redux state
and puts it in local storage
and rehydrateStore
pull all the applicationState
in local storage if present and puts it in redux store
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga';
import decoristReducers from '../reducers/decorist_reducer'
import sagas from '../sagas/sagas';
const sagaMiddleware = createSagaMiddleware();
/**
* Add all the state in local storage
* @param getState
* @returns {function(*): function(*=)}
*/
const localStorageMiddleware = ({getState}) => { // <--- FOCUS HERE
return (next) => (action) => {
const result = next(action);
localStorage.setItem('applicationState', JSON.stringify(
getState()
));
return result;
};
};
const reHydrateStore = () => { // <-- FOCUS HERE
if (localStorage.getItem('applicationState') !== null) {
return JSON.parse(localStorage.getItem('applicationState')) // re-hydrate the store
}
}
const store = createStore(
decoristReducers,
reHydrateStore(),// <-- FOCUS HERE
applyMiddleware(
sagaMiddleware,
localStorageMiddleware,// <-- FOCUS HERE
)
)
sagaMiddleware.run(sagas);
export default store;
回答5:
I cannot answer @Gardezi but an option based on his code could be:
const rootReducer = combineReducers({
users: authReducer,
});
const localStorageMiddleware = ({ getState }) => {
return next => action => {
const result = next(action);
if ([ ACTIONS.LOGIN ].includes(result.type)) {
localStorage.setItem(appConstants.APP_STATE, JSON.stringify(getState()))
}
return result;
};
};
const reHydrateStore = () => {
const data = localStorage.getItem(appConstants.APP_STATE);
if (data) {
return JSON.parse(data);
}
return undefined;
};
return createStore(
rootReducer,
reHydrateStore(),
applyMiddleware(
thunk,
localStorageMiddleware
)
);
the difference is that we are just saving some actions, you could event use a debounce function to save only the last interaction of your state