Redux 앱에서 localStorage에 쓸 위치는 어디입니까?


답변:


223

감속기는 순수하고 부작용이 없어야하기 때문에이 작업을 수행하기에 적절한 장소는 아닙니다.

구독자에서 수행하는 것이 좋습니다.

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

상점을 작성하기 전에 지속 된 파트를 읽으십시오.

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

사용 combineReducers()하면 상태를받지 못한 감속기가 기본 state인수 값을 사용하여 정상적으로 "부팅"됩니다 . 이것은 매우 편리 할 수 ​​있습니다.

localStorage에 너무 빨리 쓰지 않거나 구독자에게 문제를 일으키지 않으면 성능 문제가 발생하는 것이 좋습니다.

마지막으로,이를 대안으로 캡슐화하는 미들웨어를 작성할 수 있지만, 더 간단한 솔루션이기 때문에 구독자로 시작합니다.


1
주 일부만 구독하려면 어떻게해야합니까? 가능합니까? 나는 약간 다른 것을 계속했다. 1. redux 외부에서 지속 상태를 저장하십시오. 2. redux 조치를 사용하여 반응 생성자 (또는 componentWillMount 포함) 내부에 지속 상태를로드하십시오. 저장소에 지속 된 데이터를 직접로드하는 대신 2가 괜찮습니까? (SSR을 위해 별도로 유지하려고합니다). 그건 그렇고 Redux에 감사드립니다! 그것은 큰 프로젝트 코드로 길을 잃은 후 굉장합니다. 이제 간단하고 예측
가능해졌습니다.)

store.subscribe 콜백 내에서 현재 상점 데이터에 대한 전체 액세스 권한을 가지므로 관심있는 부분을 모두 유지할 수 있습니다.
Bo Chen

35
Dan Abramov도 전체 비디오를 만들었습니다 : egghead.io/lessons/…
NateW

2
모든 상점 업데이트에서 상태를 직렬화하는 데 정당한 성능 문제가 있습니까? 브라우저가 별도의 스레드에서 직렬화됩니까?
Stephen Paul

7
이것은 잠재적으로 낭비로 보입니다. OP는 국가의 일부만 지속될 필요가 있다고 밝혔다. 100 개의 다른 키가있는 상점이 있다고 가정하십시오. 드물게 변경되는 3 개만 유지하려고합니다. 이제 100 개의 키를 조금씩 변경할 때마다 로컬 저장소를 구문 분석하고 업데이트하고 있습니다. 지속하고 싶은 3 개의 키 중 어느 것도 변경되지 않았습니다. 아래 @Gardezi의 솔루션은 미들웨어에 이벤트 리스너를 추가하여 실제로 필요할 때만 업데이트 할 수 있도록하는 더 나은 방법입니다 . codepen.io/retrcult/pen/qNRzKN
Anna T

153

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'))
                       : {}

그런 다음이 persistedState상수를 다음 createStore과 같이 메소드에 전달 하십시오.

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

6
추가 종속성없이 간단하고 효과적입니다.
AxeEffect

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 storage하고 rehydrateStore모든 당겨 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

글쎄, 그것은 어쨌든이 좋은 생각을 가지고 작동합니까? 질문 : 데이터가 큰 다중 리듀서로 데이터를 더 많이 사용하는 경우 어떻게됩니까?
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 's Medium article ) 에서 제공되는 우수한 제안과 짧은 코드 발췌를 바탕 으로 완벽한 솔루션이 있습니다!

로컬 스토리지와 상태를 저장 /로드하는 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 를 랩핑하는 제공자로 전달 될 수 있습니다 .

// 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"]
}

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.