728x90
728x90

로컬 스토리지(LocalStorage)에 내용을 저장하고, 전역 상태 관리 라이브러리(Redux)와 동기화 하는 방법

들어가며

  • 리액트(React.js)에서 특정 변수를 로컬 스토리지(Local Storage)에 저장하고, 전역 상태 관리 라이브러리(Redux)와 동기화 시키는 방법을 정리해본다.
    • 이렇게 함으로써, 사용자의 입력을 받아 특정 변수를 화면에 표시해줘야 할 경우 새로고침을 해도 동일한 내용이 표시되게 할 수 있다.

 

로컬 스토리지(Local Storage)

개념

  • 브라우저에 데이터를 영구적으로 저장할 수 있는 방법
  • 브라우저가 닫혀도 데이터가 유지되며, 도메인별로 구분되어 저장된다.
  • 로컬 스토리지는 키-값(Key-Value) 형식으로 데이터를 저장하며, 데이터는 문자열 형식으로만 저장할 수 있다.
    • 따라서 객체(Object)배열(Array) 같은 복잡한 데이터는 JSON으로 변환하여 저장하고, 가져올 때 다시 파싱(Parsing)해야 한다.
      • JSON으로 변환하기 위해서는 @JSON.stringfy(객체|배열)@ 메서드를 사용하고, JSON을 다시 객체/배열로 파싱하려면 @JSON.parse(JSON변수)@를 사용한다.
  • 저장된 데이터는 사용자가 명시적으로 삭제하거나, 브라우저의 캐시를 지우기 전까지 유지된다.
  • 최대 저장 용량은 보통 5MB 정도이다.
  • 문자열 형태로만 데이터를 저장한다. (객체 저장 시 JSON으로 변환)

 

사용 예제 : JSON 변환 & 파싱(Parsing)

객체
const user = {
    name: 'Peter',
    age: 2024,
    isMember: true
};

// 객체 -> 문자열 형태로 변환
const userJson = JSON.stringfy(user);     

// 로컬 스토리지에 저장
localStorage.setItem('user', userJson);    

// 파싱
const storedUser = JSON.parse(localStorage.getItem('user'));

 

배열
const favoriteColors = ['red', 'blue', 'green'];

// 객체 -> 문자열 형태로 변환
const favoriteColorsJson = JSON.stringfy(favoriteColors);    

// 로컬 스토리지에 저장
localStorage.setItem('colors', favoriteColorsJson);   

// 파싱
const storedColors = JSON.parse(localStorage.getItem('colors'));

 

객체 + 배열이 혼합된 형태
const settings = {
    theme: 'dark',
    shortcuts: ['Ctrl+S', 'Ctrl+C', 'Ctrl+V'],
    layout: {
        header: true,
        sidebar: false
    }
};

// 객체+배열 -> 문자열 형태로 변환
const settingsJson = JSON.stringfy(settings);    

// 로컬 스토리지에 저장
localStorage.setItem('settings', settingsJson);    

// 파싱
const storedSettings = JSON.parse(localStorage.getItem('settings'));

 

주요 메서드

  • @setItem(key, value)@ : 로컬 스토리지에 데이터 저장하기
  • @getItem(key)@ : 로컬 스토리지에서 데이터 가져오기
  • @removeItem(key)@ : 특정 데이터를 삭제하기
  • @clear()@ : 로컬 스토리지의 모든 데이터를 삭제하기
  • @length@ : 로컬 스토리지에 저장된 데이터의 개수 반환

 

사용 예제 : 주요 메서드

데이터 저장
localStorage.setItem('username', 'peter');

const user = { name: 'peter', age: 2024 };

// 로컬 스토리지에 데이터 저장하기
localStorage.setItem('user', JSON.stringify(user));

 

데이터 가져오기
// 로컬 스토리지에서 데이터 가져오기
const username = localStorage.getItem('username');
console.log(username);  // peter

const storedUser = JSON.parse(localStorage.getItem('user'));

console.log(storedUser);  // { name: 'peter', age: 2024 }

 

데이터 삭제하기
// 특정 데이터 삭제하기
localStorage.removeItem('username');

// 전체 데이터 삭제하기
localStorage.clear();

 

(참고) 쿠키(Cookie) vs. 로컬 스토리지(Local Storage) vs. 세션(Session)

  로컬 스토리지(Local Storage) 쿠키(Cookie) 세션(Session)
 저장 위치 클라이언트 클라이언트 서버
저장 용량 5~10MB 4KB 서버 설정에 따라 다르다.
유효 기간 영구적
(사용자가 명시적으로 삭제하지 않는 한 유지)
설정 가능
(세션 쿠키는 브라우저 종료 시 삭제)
브라우저 종료 시 삭제
(서버에서 설정 가능)
데이터 전송 서버로 전송되지 않음. 서버로 자동 전송 서버에서 관리,
클라이언트에는 세션 ID만 저장
보안 클라이언트 자바스크립트로 접근 가능, 보안 취약 @HttpOnly@, @Secure@ 옵션으로 보호 가능 서버에서 관리, 비교적 안전
사용 목적 클라이언트 측 영구 데이터 저장 세션 관리, 인증 정보 유지, 상태 저장 사용자 인증 정보 등 일시적 상태 저장

 

리덕스(Redux)와 로컬 스토리지의 값 동기화 하기

  • 전역 상태 관리 라이브러리인 리덕스(Redux)와 로컬 스토리지의 값을 동기화하여 페이지가 새로고침 되더라도 값이 유지되도록 해보자.
  • 여러개의 객체를 담고 있는 배열을 로컬 스토리지에 저장하는 경우를 생각해본다.
    • 예: @[{id: *, text: *, translatedText: *}, ..., {id: *, text: *, translatedText: *}]@
const data = {
  id: Date.now(),
  text,
  translatedText,
};

 

필요한 패키지 설치

  • 우선, 필요한 패키지들을 설치해준다.
$ npm install redux-persist    # 상태를 영구적으로 저장할 수 있게 해주는 도구
$ npm install @reduxjs/toolkit    # 리덕스를 더 간편하고 효율적으로 사용하기 위해 만들어진 도구

 

/store/textSlice.js
  • 여러 객체를 담고 있는 배열(Array)를 로컬 스토리지에 저장할 것이기 때문에 @initialState@의 @textArrays@ 값을 배열(@[]@) 형태로 지정해준다.
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  textArrays: [],
};

const textSlice = createSlice({
  name: 'text',
  initialState,
  reducers: {
    // 상태를 업데이트 하는 리듀서 함수
    saveText: (state, action) => {
      state.textArrays.push(action.payload);
    },
  },
});

export const { saveText } = textSlice.actions;

export default textSlice.reducer;

 

/store/store.js
  • @store@ 변수 정의 시, @middleware@ 부분은  리덕스 툴킷에서 미들웨어를 설정할 때, @redux-persist@와 관련된 액션들이 직렬화 검사에서 무시되도록 하는 설정이다.
    • 이 부분을 설정하지 않으면 개발자 도구에서 경고 메시지가 출력된다.
    • 리덕스 툴킷과 @redux-persist@의 직렬화 방식에 차이가 있어서 발생하는 오류이다.
      • 리덕스(Redux)에서는 상태(state)나 액션(action)이 직렬화 가능한(serializable) 데이터여야 한다는 제약이 있다.
        • 직렬화 가능한 데이터란, JSON으로 변환 가능한 데이터(객체, 배열, 문자열, 숫자 등)를 의미 한다.
        • 리덕스에서 직렬화되지 않은 값(함수, DOM 요소 등)을 사용하면 예기치 않은 동작이 발생할 수 있기 때문에, 리덕스 툴킷은 기본적으로 직렬화 검사를 활성화한다.
      • 그러나 @redux-persist@에서 사용하는 액션(@persist/PERSIST@, @persist/REHYDRATE@)은 직렬화되지 않은 데이터를 포함할 수 있으므로, 이 액션들이 직렬화 검사에서 제외되도록 설정해야 한다.
        • 그렇지 않으면 경고나 오류가 발생할 수 있다.
import { combineReducers } from 'redux';
import { persistReducer, persistStore } from 'redux-persist';
import { configureStore } from '@reduxjs/toolkit';
import storage from 'redux-persist/lib/storage';   // 기본적으로 로컬 스토리지 사용하기

import textReducer from './textSlice';

// redux-persist 설정
const persistConfig = {
  key: 'root',    // 로컬 스토리지에 저장할 데이터의 이름 설정
  storage,     // 저장소로 로컬 스토리지 사용하기
};

// 여러 리듀서를 결합한 하나의 리듀서 객체
const rootReducer = combineReducers({
  text: textReducer,
});

// 퍼시스트 리듀서 생성 (기존의 rootReducer에 persistConfig를 적용한다.)
const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  // 리덕스에서 미들웨어를 설정할 때, redux-persist 관련된 액션을 무시하도록 설정
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
      },
    }),
});

// 리덕스 스토어에 redux-persist를 적용하여 저장소와 리덕스 스토어 간의 동기화를 관리하는 객체 생성
// persistor는 저장된 상태를 복원하고, 리덕스 상태가 변할 때마다 자동으로 저장소에 상태를 동기화한다.
export const persistor = persistStore(store);

 

  • 비직렬화 관련 오류가 발생할 경우, 다음과 같이 간단하게 @getDefaultMiddleware@의 @serializableCheck@를 @false@로 설정해준다.
// 스토어 설정
export const store = configureStore({
  reducer: {
    global: persistedReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: false, // 비직렬화 값 무시
    }),
});

 

/App.jsx
  • 리덕스의 @Provider@로 내부의 컴포넌트들을 감싸준다.
import { Provider } from 'react-redux';
import { store } from './store/store';

export const App = () => {
  return (
    <Provider store={store}>
      ...
    </Provider>
  );
};

export default App;

 

/src/Test.jsx
  • @saveText(data)@ 액션을 리덕스 스토어에 디스패치하여 상태를 변경시킨다.
  • 그리고 해당 값을 로컬 스토리지에 저장시켜준다.
import { useDispatch } from 'react-redux';
import { saveText } from '../store/textSlice';

export const App = () => {
    const dispatch = useDispatch();
    
    // ...
    
    if (text && translatedText) {
        const data = {
            id: Date.now(),
            text,
            translatedText,
        };

        // Redux에 저장
        dispatch(saveText(data));

        // LocalStorage에 저장
        localStorage.setItem('savedTextPair', JSON.stringify(data));
    }
}

 

참고 사이트

 

HTTP 쿠키 - HTTP | MDN

HTTP 쿠키(웹 쿠키, 브라우저 쿠키)는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각입니다. 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재 요청 시 저장된 데이

developer.mozilla.org

 

Window.localStorage - Web API | MDN

localStorage 읽기 전용 속성을 사용하면 Document 출처의 Storage 객체에 접근할 수 있습니다. 저장한 데이터는 브라우저 세션 간에 공유됩니다. localStorage는 sessionStorage와 비슷하지만, localStorage의 데이

developer.mozilla.org

 

전형적인 HTTP 세션 - HTTP | MDN

HTTP와 같은 클라이언트-서버 프로토콜에서, 세션은 다음의 세 가지 과정으로 이루어집니다.

developer.mozilla.org

 

redux-persist

persist and rehydrate redux stores. Latest version: 6.0.0, last published: 5 years ago. Start using redux-persist in your project by running `npm i redux-persist`. There are 1425 other projects in the npm registry using redux-persist.

www.npmjs.com

 

Persist state with Redux Persist using Redux Toolkit in React - LogRocket Blog

With Redux Persist, developers can save the Redux store in persistent storage so even after refreshing the browser, the site state will be preserved.

blog.logrocket.com

 

728x90
728x90