Recoil localStorage 연결 및 sessionStorage

hodu·2023년 5월 16일
0

Lessons Learned

목록 보기
6/6
post-thumbnail

상태관리 라이브러리를 쓰다보면 로컬스토리지랑 연동해서 사용하는 경우가 있다.
새로고침과 페이지 이동시 상태가 사라질 수 있어서 이를 유지하기 위함이다.

그래서 두가지의 방법을 찾았다.
하나는 Recoil-persist 라이브러리를 사용하는 것이고,
(나중에 찾아보니 버전 업데이트가 되지않는다)
(https://velog.io/@timosean/Web-Recoil-persist-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0)

하나는 리코일의 내장기능을 사용하는 것이었다.
(https://recoiljs.org/ko/docs/guides/atom-effects#local-storage-persistence-%EB%A1%9C%EC%BB%AC-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EC%A7%80%EC%86%8D%EC%84%B1)

라이브러리를 추가적으로 설치하면 더 간단하겠지만, Recoil을 설치했다면 활용도를 높이는 것이 효율적이어서 방향을 설정하였다.




Local Storage Persistence (로컬 스토리지 지속성)

공식문서를 살펴보면 아래와 같이 정의하였다.

Atom Effects는 atom 상태를 브라우저 로컬 스토리지에서 유지하기 위해서 사용될 수 있습니다. localStorage 는 동기식이므로 데이터를 async/await 혹은 Promise 없이 직접 받아올 수 있습니다.

이를 바탕으로 코드를 보면

const localForageEffect = key => ({setSelf, onSet}) => {
  setSelf(localForage.getItem(key).then(savedValue =>
    savedValue != null
      ? JSON.parse(savedValue)
      : new DefaultValue() // Abort initialization if no value was stored
  ));

  // Subscribe to state changes and persist them to localForage
  onSet((newValue, _, isReset) => {
    isReset
      ? localForage.removeItem(key)
      : localForage.setItem(key, JSON.stringify(newValue));
  });
};

const currentUserIDState = atom({
  key: 'CurrentUserID',
  default: 1,
  effects: [
    localForageEffect('current_user'),
  ]
});

로 적혀있는데, 하나씩 살펴보자.

코드 리뷰

localForageEffect는 먼저 localStorage에 저장되어있는 key 값을 받는다.

const localForageEffect = key => ({setSelf, onSet}) => {
  setSelf(localForage.getItem(key).then(savedValue =>
    savedValue != null
      ? JSON.parse(savedValue)
      : new DefaultValue() // Abort initialization if no value was stored
  ));

그다음으로 setSelf라는 거싱 주어지는데 이는 atom 값을 변경하는 함수이다.
그안에서 로직이 돌아가는데

  1. getItem 메서드를 사용해 해당 값을 받아온다.
  2. savedValue(저장된 값)이 있으면 그 값을 변환(parse)하여 사용한다.
  3. 만약 값이 없다면 기본 초기값을 사용한다.

는 방식으로 돌아간다. 일종의 초기 세팅이다.
local 값을 사용할 것인지? 설정한 값을 설정할지 판단해준다.

그다음으로는

  // Subscribe to state changes and persist them to localForage
  onSet((newValue, _, isReset) => {
    isReset
      ? localForage.removeItem(key)
      : localForage.setItem(key, JSON.stringify(newValue));
  });

onSet은 Recoil Atom값이 변경될때 실행되는 콜백함수이다.

1번째 인자
먼저 newValue는 atom의 새로운 값이다. 변경된 값을 의미한다.

2번째 인자
기존의 값을 의미한다 사용하지 않아서 생략했다.

3번째 인자
초기화 여부를 반환한다. 초기화 될 시에 기존 로컬스토리지 값을 삭제하고,
초기화가 아니라 값이 변경되는 경우에는 새로운 값을 로컬스토리지에 저장한다.

JSON.parse : JSON형식의 문자열을 파싱하여 JavaScript 객체로 변환한다.
JSON.stringify : JavaScript 객체나 값들을 JSON 형식의 문자열로 변환한다.

TS 추가

import { AtomEffect, atom } from "recoil";

const localStorageEffect: <T>(key: string) => AtomEffect<T> =
  (key: string) =>
  ({ setSelf, onSet }) => {
    const savedValue = localStorage.getItem(key);
    if (savedValue != null) {
      setSelf(JSON.parse(savedValue));
    }

    onSet((newValue, _, isReset) => {
      isReset
        ? localStorage.removeItem(key)
        : localStorage.setItem(key, JSON.stringify(newValue));
    });
  };

localStorage에 key 값이 string으로 들어와서 작성하였다.

T는 제네릭 타입 매개변수로서, 실제로 사용되는 타입이 함수를 호출할 때 지정이 된다. T는 타입 변수로서 어떤 타입이든 대체될 수 있다.

<T>(key: string) => AtomEffect<T>에서 <T>는 함수 시그니처에서 제네릭 타입을 선언하는 부분입니다. 이렇게 선언된 제네릭 타입은 함수 내부에서 T를 실제 타입으로 사용할 수 있도록 도와준다.

제네릭을 사용함으로써 함수를 여러 곳에서 재사용하고, 타입의 일반화와 추상화를 달성할 수 있습니다. 호출하는 시점에서 실제 타입을 전달하여 함수가 해당 타입에 대해 작동하도록 할 수 있다.

예시

const myEffect: AtomEffect<number> = myEffectFunction<number>('myKey');

이렇게 하면 myEffect는 AtomEffect<number> 타입으로 선언되고, myEffectFunction 함수 내에서 T는 number로 대체됩니다. 이를 통해 myEffect는 number 타입에 대한 atom effect를 나타내게 됩니다. 다른 타입을 전달하여 T를 다른 타입으로 대체할 수도 있습니다.

next.js 고려하기


next.js에서는 브라우저 기능과 서버와 분리해야한다.

useEffect와 typeof window !== "undefined"를 통해 해결할 수 있다.

SessionStorage로 수정

개인 정보와 AccessToken을 로컬스토리지에 저장했지만, 사이트 중간에 나가면 이 데이터들이 그대로 남기때문에 이를 막기위해 세션스토리지로 수정하였다.

const sessionStorageEffect: <T>(key: string) => AtomEffect<T> =
  (key: string) =>
  ({ setSelf, onSet }) => {
    if (typeof window !== "undefined") {
      const savedValue = sessionStorage.getItem(key);//수정
      if (savedValue != null) {
        setSelf(JSON.parse(savedValue));
      }

      onSet((newValue, _, isReset) => {
        isReset
          ? sessionStorage.removeItem(key)//수정
          : sessionStorage.setItem(key, JSON.stringify(newValue));//수정
      });
    }
  };

세션스토리지로 수정하였고, 새로고침이나 이동에도 문제없이 잘작동하였다.


이후 탭을 끄고 다시 들어와보니

비어져있었다

원하던 대로 잘 작동하였다 굿!




마무리


새로고침 이후에도 성공적으로 저장이 되었다.

확실히 내장 기능을 활용하는 것이 부드럽고 호환이 잘되어서 상쾌하다.

이 공부를 통해서 배운 점은 제네릭 타입 매개변수인 T에 대해 이해도를 키웠다.
타입스크립트를 제대로 공부해본 적 없어서 궁금했는데 이 기회를 통해 알게 되었고,
세션스토리지를 사용하였는데, 이론으로 공부했던 내용을 실제로 사용해본 경험이 좋았다.

앞으로도 내가 배운 이론들을 적용시키도록 노력하겠다.


기타 출처:
https://velog.io/@ctdlog/Recoil-Local-Storage-Persistence#2%EF%B8%8F%E2%83%A3-local-storage%EB%A5%BC-%ED%86%B5%ED%95%B4-recoil-state-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0

profile
잘부탁드립니다.

4개의 댓글

comment-user-thumbnail
2023년 5월 21일

저도 가 애매했는데 덕분에 잘 이해하고 갑니다! 전 recoil-persist 사용했는데 이렇게 내장 기능도 있었군요... 앞으로 더 잘 찾아보면서 사용해봐야겠습니다 .. ㅎㅎㅜ

답글 달기
comment-user-thumbnail
2023년 5월 21일

디테일이 좋습니다 !!

답글 달기
comment-user-thumbnail
2023년 5월 21일

설명이 자세해서 같이 공부한 느낌이네요ㅎㅎ 제네릭 타입도 지금까지 명확하게 이해를 못했었는데 이해했습니다..👍 잘 읽었습니다!

답글 달기
comment-user-thumbnail
2023년 5월 21일

내장 기능도 있다는 걸 왜 오늘 알았을까요.. 바보.. 공부하구 나중에 참고 하겠습니다. next.js의 고질병 window 조건문은 여기서도 나오는 군요 ㅋㅋㅋ 잘보고 갑니다

답글 달기