Javascript 在 Redux 应用程序中写入 localStorage 的位置?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35305661/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 17:33:55  来源:igfitidea点击:

Where to write to localStorage in a Redux app?

javascriptreduxlocal-storagestate

提问by Marco de Jongh

I want to persist some parts of my state tree to the localStorage. What is the appropriate place to do so? Reducer or action?

我想将状态树的某些部分保留到 localStorage。什么地方适合这样做?减速器还是动作?

回答by Dan Abramov

Reducer is never an appropriate place to do this because reducers should be pure and have no side effects.

Reducer 永远不是一个合适的地方,因为 reducer 应该是纯粹的并且没有副作用。

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 stateargument value. This can be pretty handy.

如果您使用,combineReducers()您会注意到尚未收到状态的减速器将使用其默认state参数值正常“启动” 。这可能非常方便。

It is advisable that you debounce your subscriber so you don't write to localStorage too fast, or you'll have performance problems.

建议您对订阅者进行去抖,以免写入 localStorage 太快,否则会出现性能问题。

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.

最后,您可以创建一个封装它的中间件作为替代方案,但我会从订阅者开始,因为它是一个更简单的解决方案并且可以很好地完成工作。

回答by Andrew Samuelsen

To fill in the blanks of Dan Abramov's answer you could use store.subscribe()like this:

要填写 Dan Abramov 的答案的空白,您可以store.subscribe()像这样使用:

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

Before creating the store, check localStorageand parse any JSON under your key like this:

在创建商店之前,检查localStorage并解析您的密钥下的任何 JSON,如下所示:

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

You then pass this persistedStateconstant to your createStoremethod like this:

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

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

回答by David L. Walsh

In a word: middleware.

一句话:中间件。

Check out redux-persist. Or write your own.

查看redux-persist。或者自己写。

[UPDATE 18 Dec 2016] Edited to remove mention of two similar projects now inactive or deprecated.

[2016 年 12 月 18 日更新] 已编辑删除提及两个现在已不活动或已弃用的类似项目。

回答by Gardezi

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 middlewarethings just focus on two things localStorageMiddlewareand reHydrateStoremethod. the localStorageMiddlewarepull all the redux stateand puts it in local storageand rehydrateStorepull all the applicationStatein local storage if present and puts it in redux store

如果有人对上述解决方案有任何问题,您可以自己写。让我告诉你我做了什么。忽略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;

回答by Douglas Caina

I cannot answer @Gardezi but an option based on his code could be:

我无法回答@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
    )
);

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

不同之处在于我们只是保存一些动作,您可以使用 debounce 函数仅保存您状态的最后一次交互

回答by movcmpret

I'm a bit late but I implemented a persistent state according to the examples stated here. If you want to update the state only every X seconds, this approach may help you:

我有点晚了,但我根据此处所述的示例实现了持久状态。如果您只想每 X 秒更新一次状态,这种方法可能会帮助您:

  1. 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!")
        }
    }
    
  2. Call a wrapper function in your subscriber

        store.subscribe((state) =>
        {
        updateLocalStorage(store.getState())
         });
    
  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())
         });
    

In this example, the state is updated at mosteach 5 seconds, regardless how often an update is triggered.

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

回答by CanProgram

Building on the excellent suggestions and short code excerpts provided in other answers (and Jam Creencia's Medium article), here's a complete solution!

基于其他答案(以及Jam Creencia 的 Medium 文章提供的出色建议和简短代码摘录,这里有一个完整的解决方案!

We need a file containing 2 functions that save/load the state to/from local storage:

我们需要一个包含 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;
  }
};

Those functions are imported by store.jswhere we configure our store:

这些函数由store.js导入,我们在其中配置我们的商店:

NOTE: You'll need to add one dependency: npm install lodash.throttle

注意:您需要添加一个依赖项: 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;

The store is imported into index.jsso it can be passed into the Provider that wraps App.js:

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

Note that absolute imports require this change to YourProjectFolder/jsconfig.json- this tells it where to look for files if it can't find them at first. Otherwise, you'll see complaints about attempting to import something from outside of src.

请注意,绝对导入需要对YourProjectFolder/jsconfig.json进行此更改- 如果一开始找不到文件,这会告诉它在哪里查找文件。否则,您会看到有关尝试从src外部导入某些内容的抱怨。

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