Where to write to localStorage in a Redux app?

2019-01-02 20:20发布

问题:

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



标签: