Web Storage(localStorage, sessionStorage, cookie)

미음제

·

2022. 11. 30. 18:08

2주 전, K사 코딩 테스트를 응시하며 React Redux를 사용해야 했다. Redux를 공부했을 때 간단한 예제 정도만 구현해보고, 프로젝트를 할 때도 직접적으로 Redux를 사용하지 않아서 사용 방법을 까먹고 문제를 제대로 풀지 못했다. 다시 대략적인 사용법을 익히고자 간단한 예제를 만들었고, 해당 예제를 만들면서 state가 새로고침 혹은 브라우저 종료 후에도 유지될 수 있도록 Web Storage에 저장하는 방법을 적용해봤던 것을 기록하고자 한다. Web Storage에 저장하는 예제를 작성하면서 Web Storage에 대한 내용이 가물가물해서 다시 정리했다.

 

Redux 예제

 

Reducer

 

action 객체를 받아 state를 변경할 reducer들이다.

 

const initialState = {
  count: 0,
};

// store/reducer/counter/index.js
export const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case "PLUS":
      return {
        ...state,
        count: state.count + action.count,
      };
    case "MINUS":
      return {
        ...state,
        count: state.count - action.count,
      };
    case "INIT":
      return {
        ...state,
        count: 0,
      };
    default:
      return state;
  }
};

 

// store/reducer/task/index.js
const initialState = {
  tasks: [],
};

export const taskReducer = (state = initialState, action) => {
  switch (action.type) {
    case "ADD": {
      const { task, count } = action.task;
      const checkDuplicate = state.tasks.filter((item) => item.task === task);

      if (checkDuplicate.length) {
        return {
          ...state,
          tasks: state.tasks.map((item) =>
            item.task === task ? { task, count: item.count + count } : item
          ),
        };
      } else {
        return {
          ...state,
          tasks: [...state.tasks, action.task],
        };
      }
    }
    default:
      return {
        ...state,
      };
  }
};

 

Action

 

dispatch에서 받을 객체를 반환하는 action 함수이다.

 

// store/action/counterPlustAction
const counterPlusAction = (count = 1, type) => {
  return {
    type,
    count,
  };
};

export default counterPlusAction;

// store/action/counterInitAction
const counterInitAction = (count = 0) => {
  return {
    type: "INIT",
    count,
  };
};

export default counterInitAction;

// store/action/taskAction
const taskAction = (type, task) => {
  return {
    type,
    task,
  };
};

export default taskAction;

 

Redux store

 

앞서 작성한 두 개의 reducer를 하나의 reducer로 묶어주고, store로 내보내 준다.

 

// store/index.js
import { combineReducers, createStore } from "redux";
import { counterReducer } from "./reducer/counter";
import { taskReducer } from "./reducer/tasks";

const rootStore = combineReducers({ counterReducer, taskReducer });

export const store = createStore(rootStore);

 

작성한 Redux store를 Redux Provider를 통해 전달한다.

 

// root index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { store } from "./store";
import { Provider } from "react-redux";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

 

state를 변경하는 내용을 App.js에 작성한다. counter를 조작하는 버튼과 input 값과 counter 값을 결합해 새로운 task를 추가하는 내용이다. 추가된 task를 전역 store에서 관리한다. 이때 새로고침 하면 task들이 삭제되는 것을 방지하기 위해 Web Storage와 Redux-persist를 사용했다.

 

// root App.js
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import counterAction from "./store/action/counterAction";
import counterInitAction from "./store/action/counterInitAction";
import taskAction from "./store/action/taskAction";

function App() {
  const count = useSelector((state) => {
    const { counterReducer } = state;
    const { count } = counterReducer;
    return count;
  });
  const taskList = useSelector((state) => {
    const { taskReducer } = state;
    const { tasks } = taskReducer;
    return tasks;
  });

  const [task, setTask] = useState("");
  const dispatch = useDispatch();

  const handleChange = (e) => {
    const { target } = e;
    const { value } = target;

    setTask(value);
  };

  const handleSubmit = () => {
    if (!task || count === 0) {
      console.error("Input task or count value");
      return;
    }
    dispatch(taskAction("ADD", { task, count }));
  };

  return (
    <>
      <div>Redux Example</div>
      <div style={{ display: "flex", gap: "1rem" }}>
        <label htmlFor="input">Task</label>
        <input type="text" onChange={handleChange} />
        <div>Count : {count}</div>
        <div>
          <button onClick={() => dispatch(counterAction(1, "PLUS"))}>+</button>
          <button onClick={() => dispatch(counterAction(1, "MINUS"))}>-</button>
          <button onClick={() => dispatch(counterInitAction())}>
            Initialize
          </button>
        </div>
        <button onClick={handleSubmit}>submit</button>
      </div>
      <ul>
        {taskList.map((item) => {
          const { task, count } = item;
          return (
            <li key={item.task}>
              {task}:{count}
            </li>
          );
        })}
      </ul>
    </>
  );
}

export default App;

 

Web Storage를 활용해 전역 store 값 저장하기

 

// utils/storage.js
const storage = window.localStorage;

const setItem = (key, value) => {
  try {
    console.log(value);
    storage.setItem(key, JSON.stringify(value));
  } catch (e) {
    throw new Error("localStorage setItem is fail");
  }
};

const getItem = (key, initialState = {}) => {
  try {
    const storedValue = storage.getItem(key);
    console.log(storedValue);

    if (!storedValue) {
      return initialState;
    }
    return JSON.parse(storedValue);
  } catch (e) {
    console.error("localStorage getItem is fail");
    return initialState;
  }
};

export { setItem, getItem };

 

localStroage를 활용하기 위한 setItem, getItem 함수를 정의한다.

 

// store/index.js
import { combineReducers, createStore } from "redux";
import { counterReducer } from "./reducer/counter";
import { taskReducer } from "./reducer/tasks";
import { setItem, getItem } from "../utils/storage";

const rootStore = combineReducers({ counterReducer, taskReducer });

const persistedState = getItem("taskState");

export const store = createStore(rootStore, persistedState);

store.subscribe(() => {
  const state = store.getState();
  setItem("taskState", state);
});

 

localStroage에 state를 저장하고 꺼내 오기 위해 store를 수정한다. 'taskState'라는 key 값의 storage에 저장된 값이 있다면 store의 initialState로 넘겨준다. store.subscribe() 메소드를 통해 store의 변경이 감지되면 해당 state값을 'taskState' key 값으로 storage에 업데이트한다.

 

간단하게 예제를 구현했고, localStorage와 sessionStorage의 차이가 애매하게 기억나서 해당 내용을 다시 찾아보고 정리해보았다.

 

Web Storage

 

HTML5에서 웹 데이터를 클라이언트에 저장할 수 있는 Web Storage 스펙이 포함되었다. Web Storage는 key/value 형태로 데이터를 저장하고, key를 통해 데이터를 조회한다. localStorage와 sessionStorage로 구분해 데이터의 지속성을 구분할 수 있다.

 

Web Storage와 쿠키 차이점

 

쿠키는 매번 서버로 전송된다.

 

웹에서 쿠키를 설정하면 모든 요청에 쿠키 정보를 포함해 서버로 전송한다. 이와 달리 Web Storage에서는 클라이언트에 데이터가 저장될 뿐 서버로 전송되지 않는다. Web Storage는 쿠키에 저장하는 것보다 트래픽 피용을 줄일 수 있다.

 

문자열 외에 객체 정보를 저장할 수 있다.

 

문자열 데이터 이외에 구조화된 객체를 저장할 수 있다.

 

용량 제한에 차이가 있다.

 

쿠키는 개수와 용량에 제한이 있다. 한 사이트에 저장할 수 있는 쿠키의 최대 수는 20개, 최대 쿠키 크기는 4KB이다.

Web Storage는 브라우저 별로 상이하고, 최대 5MB 크기를 권장하고 있다.

 

영속성

 

쿠키는 만료 시간이 지나면 제거가 된다. 영구적인 쿠키를 원한다면 만료일자를 굉장히 크게 잡아야 한다. 만료 일자가 지정되지 않으면 세션 쿠키가 된다. 반면 Web Storage는 만료기간의 설정이 없다.

 

LocalStorage, SessionStorage

 

로컬 스토리지

 

데이터를 저장하면 반영구적으로 데이터가 저장된다. 데이터는 키-값 형태로 저장된다. 브라우저를 종료해도 데이터가 남아있기 때문에 유효기간이 필요 없는 데이터를 저장할 때 유용하다. 쿠키와 마찬가지로 저장했던 도메인과 이용하는 도메인이 다르면 접근할 수 없다.

 

로컬 스토리지의 영속성도 동일한 컴퓨터에서 동일한 브라우저를 사용할 때만 해당된다. 다른 브라우저를 사용하면 서로 다른 브라우저에 각각 로컬 스토리지가 할당되는 개념이다.

 

브라우저를 닫아도 데이터가 유지가 되고, 사용자가 데이터에 접근할 수 있다. 서로 다른 도메인에서 접근하는 것은 막아주지만 사용자의 접근을 제한하진 않는다. 민감한 정보를 다루는 것은 지양해야 한다.

 

세션 스토리지

 

새로운 창, 새로운 탭을 생성할 때마다 개별적으로 저장되는 데이터를 관리하기 때문에 브라우저를 닫는 순간 사라지게 된다. 데이터는 키-값 형태로 저장된다. 로컬 스토리지와는 달리 세션 스토리지는 같은 도메인이더라도 세션이 다르면 데이터에 접근할 수 없다.

 

쿠키

 

클라이언트에 저장되는 키-값 형태의 값이 들어있는 작은 파일이다. 사용자 인증 유효 시간 등을 쿠키에 명시할 수 있다. 쿠키 만료 시간이 되지 않으면 브라우저가 종료되더라도 쿠키는 유지된다. 클라이언트에서 최대 300개를 저장할 수 있고, 하나의 도메인에서 20개, 최대 4KB를 저장할 수 있다.

 

Response Header에 Set-Cookie 속성을 사용해 클라이언트에 쿠키를 만들 수 있다. 쿠키는 사용자가 요청하지 않아도 자동으로 Request-Header에 담겨 서버로 요청이 보내진다.

 

쿠키의 구성 요소

 

  • 키 : 각각의 쿠키를 구별하는 데 사용되는 이름
  • 값 : 쿠키의 키와 관련된 값
  • 유효시간 : 쿠키의 유지시간
  • 도메인 : 쿠키를 전송할 도메인
  • 경로 : 쿠키를 전송할 요청 경로

 

쿠키의 동작 방식

 

  • 클라이언트가 페이지를 요청한다.
  • 서버에서 쿠키를 생성한다.
  • HTTP 헤더에 쿠키를 포함시켜 응답한다.
  • 브라우저가 종료되어도 쿠키 만료 기간이 있다면 클라이언트에서 보관하고 있는다.
  • 같은 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보낸다.
  • 서버에서 쿠키를 읽어 이전 상태 정보를 변경할 필요가 있을 때 쿠키를 업데이트하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답한다.

 


간단한 Redux 예제와 Web Storage를 활용해 store의 state를 저장하고 꺼내 쓰는 예제를 작성했고, Web Storage에 대한 정리도 새롭게 다시 했다. Web Storage를 활용해 state를 저장했는데, 찾아보니 Redux-persist를 통해 local/session Storage에 저장할 수 있는 방법도 있다고 한다. Redux-persist 내용은 다음 포스트에 작성하고자 한다.

 

참고

 

 

브라우저 저장소(웹저장소), Cookie, Session 란?

Web Storage Web Storage란 HTML5부터 제공하는 기능으로, 해당 도메인과 관련된 특정 데이터를 서버가 아니라 클라이언트 웹브라우저에 저장할 수 있도록 제공하는 기능이다. 쿠키(cookie)와 비슷한 기능

akdl911215.tistory.com

 

 

localStorage와 sessionStorage

 

ko.javascript.info

 

 

쿠키, 로컬 스토리지, 세션 스토리지

HTTP의 무상태성의 단점을 보완하는 클라이언트 사이드 데이터 저장 장치

velog.io

 

 

Store | Redux

API > Store: the core Redux store methods

redux.js.org

 

반응형

'Developer > TI' 카테고리의 다른 글

CORS  (1) 2022.12.24
Babel, Webpack(바벨, 웹팩)  (0) 2022.11.22
프로미스, 콜백 함수  (0) 2022.11.08
이벤트 버블링/캡쳐링, 이벤트 위임  (1) 2022.10.30
자바스크립트 script async/defer 어트리뷰트  (0) 2022.10.21