在Redux应用程序中哪里写入本地存储?


Answers:


223

减速器永远都不适合这样做,因为减速器应该是纯净的,没有副作用。

我建议仅在订户中执行此操作:

store.subscribe(() => {
  // persist your state
})

在创建商店之前,请阅读以下保留的部分:

const persistedState = // ...
const store = createStore(reducer, persistedState)

如果您使用combineReducers()它,则会注意到尚未接收到该状态的reducers将使用其默认state参数值正常启动。这可能非常方便。

建议您对订户进行反跳操作,以免写到localStorage的速度过快,否则会出现性能问题。

最后,您可以创建一个中间件来封装该中间件作为替代方案,但是我将从订户入手,因为它是一个更简单的解决方案,并且做得很好。


1
如果我只想订阅部分州怎么办?那可能吗?我进行了一些不同的操作:1.在redux之外保存持久状态。2.使用redux动作在react构造函数(或componentWillMount)中加载持久状态。2是完全可以的,而不是直接将持久数据加载到存储中吗?(我正尝试将其单独保留用于SSR)。顺便感谢Redux!它很棒,我喜欢它,在迷失了我的大型项目代码后,现在又回到了简单且可预测的状态:)
Mark Uretsky

在store.subscribe回调中,您可以完全访问当前商店数据,因此可以保留您感兴趣的任何部分
Bo Chen

35
丹·阿布拉莫夫(Dan Abramov)也制作了一个完整的视频:egghead.io/教训/…
NateW

2
在每个商店更新上序列化状态是否存在合理的性能问题?浏览器是否可能在单独的线程上序列化?
斯蒂芬·保罗

7
这看起来很浪费。OP确实表示只需要保留一部分状态。假设您的商店有100个不同的钥匙。您只想保留其中三个不经常更改的对象。现在,您正在解析和更新本地存储,其中包括对100个键中的任何一个的微小更改,而您对持久化感兴趣的3个键中的任何一个都没有被更改过。下面的@Gardezi解决方案是一种更好的方法,因为您可以将事件侦听器添加到中间件中,以便在实际需要时进行更新,例如:codepen.io/retrcult/pen/qNRzKN
Anna T

153

要填补丹·阿布拉莫夫的答案的空白,您可以这样使用store.subscribe()

store.subscribe(()=>{
  localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})

在创建商店之前,请localStorage像下面这样检查并解析密钥下的所有JSON:

const persistedState = localStorage.getItem('reduxState') 
                       ? JSON.parse(localStorage.getItem('reduxState'))
                       : {}

然后persistedState,您可以将此常量传递给您的createStore方法,如下所示:

const store = createStore(
  reducer, 
  persistedState,
  /* any middleware... */
)

6
简单有效,没有额外的依赖关系。
AxeEffect 2016年

1
创建一个中间件可能看起来更好一些,但是我同意它是有效的,并且在大多数情况下是足够的
Bo Chen

使用CombineReducers时,有什么好方法可以做到,而您只想保留一个存储?
brendangibson

1
如果localStorage中没有任何内容,应该persistedState返回initialState而不是一个空对象?否则,我认为createStore将使用该空对象进行初始化。
Alex

1
@Alex您是对的,除非您的initialState为空。
Link14年

47

一句话:中间件。

查看redux-persist。或自己写。

[2016年12月18日更新]经过编辑,删除了两个未激活或不推荐使用的类似项目。


12

如果上述解决方案有任何问题,您可以写信给自己。让我告诉你我做了什么。忽略saga middleware事物只关注两个事物localStorageMiddlewarereHydrateStore方法。在localStorageMiddleware拉所有redux state,并把它放在local storagerehydrateStore把所有的applicationState本地存储(如果存在),并将其放在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;

2
嗨,localStorage即使商店中的任何内容都没有变化,这也不会导致大量写入吗?您如何补偿不必要的写入
user566245 '18

好吧,不管怎么说,这个好主意有吗?问题:如果我们有更多的数据将多个化简器与大数据一起使用,会发生什么情况?
Kunvar Singh

3

我无法回答@Gardezi,但基于他的代码的选项可能是:

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
    )
);

所不同的是,我们只保存了一些操作,您可以使用去抖功能来仅保存状态的最后一次互动


1

我有点迟了,但是我根据这里所述的示例实现了持久状态。如果您只想每隔X秒更新一次状态,则该方法可以为您提供帮助:

  1. 定义包装函数

    let oldTimeStamp = (Date.now()).valueOf()
    const millisecondsBetween = 5000 // Each X milliseconds
    function updateLocalStorage(newState)
    {
        if(((Date.now()).valueOf() - oldTimeStamp) > millisecondsBetween)
        {
            saveStateToLocalStorage(newState)
            oldTimeStamp = (Date.now()).valueOf()
            console.log("Updated!")
        }
    }
  2. 在您的订户中调用包装函数

        store.subscribe((state) =>
        {
        updateLocalStorage(store.getState())
         });

在此示例中,无论触发更新的频率如何,状态最多每5秒更新一次。


1
你可以包装(state) => { updateLocalStorage(store.getState()) }lodash.throttle这样的:store.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }和删除的时间检查逻辑里面。
GeorgeMA

1

在其他答案(以及Jam Creencia的中篇文章提供的出色建议和简短代码摘录的基础上,这是一个完整的解决方案!

我们需要一个包含2个函数的文件,这些函数可以将状态保存到本地存储或从本地存储加载状态:

// FILE: src/common/localStorage/localStorage.js

// Pass in Redux store's state to save it to the user's browser local storage
export const saveState = (state) =>
{
  try
  {
    const serializedState = JSON.stringify(state);
    localStorage.setItem('state', serializedState);
  }
  catch
  {
    // We'll just ignore write errors
  }
};



// Loads the state and returns an object that can be provided as the
// preloadedState parameter of store.js's call to configureStore
export const loadState = () =>
{
  try
  {
    const serializedState = localStorage.getItem('state');
    if (serializedState === null)
    {
      return undefined;
    }
    return JSON.parse(serializedState);
  }
  catch (error)
  {
    return undefined;
  }
};

这些功能是由store.js导入的,我们在其中配置了商店:

注意:您需要添加一个依赖项: npm install lodash.throttle

// FILE: src/app/redux/store.js

import { configureStore, applyMiddleware } from '@reduxjs/toolkit'

import throttle from 'lodash.throttle';

import rootReducer from "./rootReducer";
import middleware from './middleware';

import { saveState, loadState } from 'common/localStorage/localStorage';


// By providing a preloaded state (loaded from local storage), we can persist
// the state across the user's visits to the web app.
//
// READ: https://redux.js.org/recipes/configuring-your-store
const store = configureStore({
	reducer: rootReducer,
	middleware: middleware,
	enhancer: applyMiddleware(...middleware),
	preloadedState: loadState()
})


// We'll subscribe to state changes, saving the store's state to the browser's
// local storage. We'll throttle this to prevent excessive work.
store.subscribe(
	throttle( () => saveState(store.getState()), 1000)
);


export default store;

商店被导入到index.js中,因此可以将其传递给包装App.js的Provider :

// FILE: src/index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'

import App from './app/core/App'

import store from './app/redux/store';


// Provider makes the Redux store available to any nested components
render(
	<Provider store={store}>
		<App />
	</Provider>,
	document.getElementById('root')
)

请注意,绝对导入要求对YourProjectFolder / jsconfig.json进行此更改-如果它最初无法找到文件,则会告诉它在哪里寻找文件。否则,您会看到有关尝试从src外部导入内容的抱怨。

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.