我想将我的状态树的某些部分持久化到localStorage。在什么地方这样做比较合适?减速还是行动?
当前回答
一句话:中间件。
查看redux-persist。或者自己写。
[更新2016年12月18日]编辑删除提及两个类似的项目现在不活跃或弃用。
其他回答
在其他回答(以及Jam Creencia的Medium文章)中提供的优秀建议和简短代码摘录的基础上,这里有一个完整的解决方案!
我们需要一个包含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导入的,我们在这里配置了我们的store:
注意:你需要添加一个依赖: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;
store被导入到index.js中,所以它可以传递到包装App.js的Provider中:
//文件:src/index.js import React from React 从'react-dom'导入{render} 从'react-redux'导入{Provider} import App from './ App /core/App' Import store from './app/redux/store'; //提供程序使Redux存储可用于任何嵌套组件 呈现( <供应商商店={商店}> < App / > > < /提供者, . getelementbyid(根) )
注意,绝对导入需要对YourProjectFolder/jsconfig进行更改。Json -如果它一开始找不到文件,它会告诉它去哪里找。否则,您将看到试图从src外部导入某些东西的抱怨。
{ " compilerOptions ": { “baseUrl”:“src” }, “包括”(" src "): }
如果任何人对上述解决方案有任何问题,您可以编写自己的解决方案。让我给你看看我做了什么。忽略saga中间件的事情,只关注localStorageMiddleware和reHydrateStore方法。localStorageMiddleware将所有的redux状态拉到本地存储中,rehydrateStore将所有的applicationState拉到本地存储中,如果存在的话,将其放入redux存储中
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;
为了填补Dan Abramov的答案,你可以像这样使用store.subscribe():
store.subscribe(()=>{
localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})
在创建存储之前,检查localStorage并像这样解析键下的JSON:
const persistedState = localStorage.getItem('reduxState')
? JSON.parse(localStorage.getItem('reduxState'))
: {}
然后你像这样将这个perstedstate常量传递给你的createStore方法:
const store = createStore(
reducer,
persistedState,
/* any middleware... */
)
我有点晚了,但是我根据这里的示例实现了一个持久状态。如果你想每X秒更新一次状态,这个方法可以帮助你:
Define a wrapper function 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!") } } Call a wrapper function in your subscriber store.subscribe((state) => { updateLocalStorage(store.getState()) });
在这个例子中,状态最多每5秒更新一次,而不管更新被触发的频率。
减速器从来不是一个合适的地方这样做,因为减速器应该是纯粹的,没有副作用。
我建议只在订阅者中进行:
store.subscribe(() => {
// persist your state
})
在创建存储之前,读取那些持久化的部分:
const persistedState = // ...
const store = createStore(reducer, persistedState)
如果你使用combineReducers(),你会注意到那些没有接收到状态的reducer会像正常启动一样使用默认的state参数值。这非常方便。
建议您取消订阅服务器,这样就不会太快地写入localStorage,否则就会出现性能问题。
最后,您可以创建一个中间件,将其封装为替代方案,但我会从订阅者开始,因为这是一个更简单的解决方案,而且工作做得很好。
推荐文章
- 一元加/数字(x)和parseFloat(x)之间的区别是什么?
- angularjs中的compile函数和link函数有什么区别
- 删除绑定中添加的事件监听器
- 很好的初学者教程socket.io?
- HtmlSpecialChars在JavaScript中等价于什么?
- React: 'Redirect'没有从' React -router-dom'中导出
- 如何在React中使用钩子强制组件重新渲染?
- 我如何使用Jest模拟JavaScript的“窗口”对象?
- 我如何等待一个承诺完成之前返回一个函数的变量?
- 在JavaScript中根据键值查找和删除数组中的对象
- 使嵌套JavaScript对象平放/不平放的最快方法
- 如何以及为什么'a'['toUpperCase']()在JavaScript工作?
- 有Grunt生成index.html不同的设置
- 文档之间的区别。addEventListener和window。addEventListener?
- 如何检查动态附加的事件监听器是否存在?