하루살이 개발일지

recoil-persist와 atomFamily를 사용해 session storage에 atom 저장하기 본문

프로젝트

recoil-persist와 atomFamily를 사용해 session storage에 atom 저장하기

harusari 2023. 8. 23. 14:07

recoil-persist와 atomFamily를 사용해 session storage에 atom 저장하기

상황

  • ‘해결된 민원이에요’ 버튼이 4에서 5로 넘어가는 순간, 모달을 띄워 해결된 게시물임을 알려야 했던 상황
  • 해당 모달은 게시글마다 모달 창을 ‘단 한 번만’ 띄워서 알려주어야 했음

 

시도한 것들


1. alert여부를 local state로 만들어 관리

const [isDoneAlerted, setisDoneAlerted] = useState<boolean>(false);

useEffect(() => {
    if (localDoneCount === 5 && !isDoneAlerted) {
      setisDoneAlerted(true);
      openModal(EModalType.POP_UP, {
        title: '해결 완료 처리된 게시물입니다',
        cancelButton: false,
        functionButton: {
          label: '닫기',
          onClick: () => {
            closeModal();
          },
          theme: 'emptyBlue',
        },
      });
    }
  }, [localDoneCount]);

처음에는 모달 창이 alert되었는지 여부를 useState로 관리하였다.

그러나, useState로 alert여부를 관리하게 되면 해당 페이지가 언마운트될 때 state의 값이 초기화되어, 단 한 번만 모달창을 띄우는 것이 불가능했다.

 

2. alert여부를 recoil state로 만들어 관리

// state.ts
export const $isDoneAlerted = atom({
  key: 'isDoneAlerted',
  default: false,
});

// 사용하는 곳
const [isDoneAlerted, setisDoneAlerted] = useRecoilState($isDoneAlerted);

두 번째 시도에서는 페이지가 언마운트되어도 alert의 여부가 recoil로 관리되고 있어서 모달 창이 두 번 나오는 상황은 해결하였다.

그러나 이 방식에도 여러 가지 문제점이 있었는데,

  1. 각각의 게시물마다 alert 여부를 저장해야 하는데, 단지 한 번 이상 alert 되면 atom값이 true로 바뀌어 게시물들을 구분하지 못하는 문제
  2. 로그인, 로그아웃 기능 구현시 window.location.reload()로 페이지를 새로고침하는 경우가 있는데, 이 경우 recoil 값이 초기화되는 문제

즉 ‘게시물마다’ alert를 띄웠는지 안 띄웠는지 여부를 각각 저장하고, ‘새로고침되어도’ 값이 남아있어야 했다.

 

 

atomFamily와 recoil-persist에 대하여


이는 atomFamily와 recoil-persist 라이브러리의 사용으로 해결할 수 있었다.

atomFamily란?

atomFamily의 정의

  • Recoil API중 하나로, parameterized (파라미터화된) 상태를 생성할 수 있는 함수
    • parameterize(파라미터화)란? 특정 파라미터나 매개변수를 받아 그에 따라 동작이나 값이 달라지는 것을 의미
  • 기본적으로 atom은 고정된 상태를 나타내지만, atomFamily는 주어진 파라미터를 기반으로 동적으로 상태를 생성할 수 있음
  • 동일한 로직과 기본값을 공유하지만, 다양한 파라미터에 따라 독립적인 상태를 갖는 atom을 생성할 수 있음

 

사용 예시

  • 사용자의 ID에 따라 사용자의 정보를 저장하는 상태를 관리할 때
  • 만약 atom만 사용한다면 각 사용자 ID마다 별도의 atom을 생성해야 함. 그러나 atomFamily를 사용하면 하나의 atomFamily에서 사용자 ID를 파라미터로 제공함으로써 동적으로 각 사용자에 대한 상태를 관리할 수 있음
import { atomFamily } from 'recoil';

// 사용자 ID를 파라미터로 받아, 해당 사용자의 정보를 관리하는 아톰을 생성
const userStateFamily = atomFamily({
  key: 'userStateFamily',
  default: null, // 기본값은 null로 설정
});

// 사용 예:
const user123State = userStateFamily(123);  // 사용자 ID 123에 대한 상태
const user456State = userStateFamily(456);  // 사용자 ID 456에 대한 상태

 

recoil-persist란?

recoil-persist의 정의

( https://www.npmjs.com/package/recoil-persist )

  • Recoil의 상태(state)를 영속적으로 저장하기 위한 라이브러리
  • 일반적으로 웹에서의 영속적인 상태 저장은 local storage나 session storage를 사용하여 이루어지지만, recoil-persist는 이러한 스토리지 매커니즘을 사용해 recoil의 상태를 자동으로 저장하고 불러올 수 있게 함

 

사용 방법

1. recoil-persist 설치하기

yarn add recoil-persist

 

2. 기본 설정하기

import { recoilPersist } from 'recoil-persist';

const { persistAtom } = recoilPersist({
  key: 'recoil-persist', // unique key for sessionStorage or localStorage (default is 'recoil-persist')
  storage: sessionStorage, // sessionStorage or localStorage (default is localStorage)
});

 

3. atom과 함께 사용하기

  • recoil의 atom을 정의할 때 ‘effects_UNSTABLE’을 사용해 ‘persistAtom’을 추가
  • 이렇게 하면 해당 atom의 상태가 스토리지에 자동으로 저장됨
import { atom } from 'recoil';

export const sampleState = atom({
  key: 'sampleState',
  default: [],
  effects_UNSTABLE: [persistAtom],
});

 

 

4. 동작 방식

  • recoil-persist는 recoil상태가 변경될 때마다 해당 상태를 자동으로 스토리지에 저장함
  • 패이지를 새로고침하거나 다시 방문했을 때 저장된 상태를 자동으로 복원함

 

5. 삭제 및 초기화

  • 스토리지에서 특정 키에 대한 데이터를 삭제하거나 초기화하는 것이 가능
  • 이 때 recoilPersist에서 반환된 updateState, resetState 함수를 사용

 

 

localStorage와 sessionStorage의 차이점

localStorage와 sessinStorage는 웹 스토리지에 속하는 매커니즘으로, 키-값 쌍을 사용해 데이터를 저장할 수 있음

이는 사용 목적, 용도, 용량 등에서 차이점이 있음

 

 

해결 방법


atomFamily와 recoil-persist 채택 이유

atomFamily

  • 각 게시글 ID마다 모달창이 열렸는지 아닌지의 여부를 각각 저장하고 싶었음
  • 이를 각각의 atom으로 구현하면 같은 로직의 다른 코드가 수없이 탄생해 보일러플레이트 코드를 유발하고, 비효율적이므로
  • 즉, 게시글 ID를 파라미터로 전달해 동적으로 atom을 생성하기 위해서

 

recoil-persist

  • 현재 애플리케이션에서 새로고침하는 로직이 많이 있어 recoil만으로는 원하는 상태를 영속적으로 저장할 수 없음
  • sessionStorage를 사용한 이유
    • 게시글 ID당 모달이 열렸는지 확인하는 데이터는 장기적으로 저장될 필요가 없음. 즉 이는 임시적인 데이터이므로 sessionStorage가 적합하다고 생각
    • 사용자가 탭을 닫으면 저장된 데이터가 초기화되는 것이 구현 목적에 더 적합하다고 생각

 

구현 방법

1. recoil-persist와 atomFamily 정의하기

import { atomFamily } from 'recoil';
import { recoilPersist } from 'recoil-persist';

const { persistAtom } = recoilPersist({
  key: 'isDoneAlerted',
  storage: sessionStorage,
});

export const $isDoneAlertedFamily = atomFamily({
  key: 'isDoneAlertedFamily',
  default: false,
  effects_UNSTABLE: (postId) => [
    ({ setSelf, onSet }) => {
      const storedValue = sessionStorage.getItem(
        `isDoneAlertedFamily_${String(postId)}`,
      );
      if (storedValue != null) {
        setSelf(JSON.parse(storedValue));
      }

      onSet((newValue) => {
        sessionStorage.setItem(
          `isDoneAlertedFamily_${String(postId)}`,
          JSON.stringify(newValue),
        );
      });
    },
  ],
});
  • recoil-persist를 설정
    • sessionStorage를 사용해 상태의 영속성을 관리하게끔 설정
  • atomFamily 정의
    • 모달창은 기본적으로 열리지 않은 상태일 것이기 때문에 아톰의 기본값은 false
  • effects_UNSTABLE
    • 아톰의 부작용을 정의하는 배열
    • 아톰 패밀리의 각 아이템마다 postId에 따라 동적으로 효과를 생성
    • 위의 경우 sessinStorage에서 상태 값을 가져오고, 상태 값이 변경될 때 sessionStorage에 저장하는 로직이 실행됨
  • setSelf
    • 아톰의 현재값을 변경하거나 업데이트하는 데 사용되는 함수
    • 위 경우 sessionStorage에서 가져온 값을 아톰에 설정하는 데 사용함
  • onSet
    • 아톰의 값이 변경될 때마다 호출하는 콜백함수
    • 아톰의 새 값과 이전 값을 인자로 받을 수 있음
    • 위 경우 아톰 값이 변경될 때마다 해당 변경사항을 sessionStorage에 저장함

+) 위 경우 postId의 타입 문제 때문에 String(postId)를 사용해 postId를 문자열로 변환함

 

 

2. 정의한 atomFamily를 사용

  const [isDoneAlerted, setisDoneAlerted] = useRecoilState(
    $isDoneAlertedFamily(postId),
  );

// 코드 중략...

  useEffect(() => {
    if (localDoneCount === 5 && !isDoneAlerted) {
      setIsReallyDone(true);
      setisDoneAlerted(true);
      openModal(EModalType.POP_UP, {
        title: '해결 완료 처리된 게시물입니다',
        cancelButton: false,
        functionButton: {
          label: '닫기',
          onClick: () => {
            closeModal();
          },
          theme: 'emptyBlue',
        },
      });
    }
  }, [localDoneCount, isReallyDone]);
  • 정의한 isDoneAlertedFamily에 postId를 파라미터로 넘겨주어 동적으로 atom을 생성
  • count가 5가 되는 순간 + isDoneAlerted가 열리지 않은 상황에 모달을 단 한 번 열 수 있음

 

결과

  • 이렇게 열렸던 게시글은 세션 스토리지에 postId마다 저장되며, 한 번 true로 값이 바뀌면 다시 alert modal이 뜨지 않게 해결