티스토리 뷰

728x90
SMALL

배경

그동안 개발하면서 참 이런 거 모호하다 라고 느꼈던 것들이 있다. 그런 것들을 정리해보겠다.

1편은 웹 스토리지, 2편은 queryParam, 3편은 props 드릴링 문제를 다룰 것이다.

웹 스토리지(로컬스토리지)

웹에서는 로컬스토리지, 세션스토리지, 쿠키 등등의 가벼운 스토리지가 있다. 그리고 모든 것을 백엔드에 저장할 수도 있지만 상황에 따라 웹 스토리지를 사용해야 하는 경우가 있다.

 

리액트와 로컬스토리지로 예시를 들어보자.

 

1. 로컬스토리지 연동하기

1. useState로 상태를 관리하고 있었다.

2. 그 상태를 로컬스토리지에 저장해달라는 요청을 받았다.

3. useState의 initialState와 setState부분에 localStorage를 연동한다.

 

문제점

localStorage를 사용할 때마다 불필요한 보일러 플레이트가 생긴다.

 

2. 로컬스토리지와 useState 연동하기

useLocalStorage라는 훅을 만들고 여기서 useState와 localStorage를 연동한다.

 

문제점

useLocalStorage라는 하나의 훅을 사용하다 보니 사용하는 localStorage key가 많아짐에 따라 관리가 어려워진다.

또한 initialState가 localStorage로부터 들어가다 보니 확실하지 않은 값이 들어가게 된다.

 

3. key별로 훅을 분리하기

key 별로 훅을 분리하면 위의 문제점들이 해결된다.

예를 들면 useLocalUser라는 훅을 만들고 내부에서 useLocalStorage를 사용한다. 그리고 useLocalUser라는 훅에서 validation을 하면 된다.

 

문제점

프로젝트가 복잡해질수록 이런 데이터를 상태관리 라이브러리에서 관리하고 싶다.

ex) useLocalUser를 두 개의 컴포넌트에서 사용하고 있다면 하나의 컴포넌트에서 상태를 변경해도 다른 컴포넌트에서 상태 변경이 반영되지 않는다. (둘 다 하나의 화면에서 보일 경우)

 

4. 상태관리 라이브러리의 미들웨어에서 로컬스토리지 변경하기

상태관리 라이브러리에서 상태를 변경하고 미들웨어에서 side effect를 처리하면 된다. 이미 많은 개발자들이 사용 중이고 redux나 recoil에서도 사용 방법이 대중화되어있다.

리코일 예시

리덕스 예시

 

문제점

다른 탭에서 로컬스토리지를 변경시켰는데 이게 반영되어야 할 경우가 있다.

 

5. storage 이벤트 사용하기

storage라는 이벤트를 이용하면 외부 탭에서 변경된 변경 사항을 알 수 있다. (옵저버 패턴 적용 가능) MDN 문서

나만 몰랐나.... 다들 알고 있었던 건가.... gpt가 알려줬다...

 

예시 코드

window.addEventListener('storage', (e) => {
  if (e.key === 'myKey') {
    console.log(`New value for myKey: ${e.newValue}`);
    // 여기에서 필요한 로직을 수행합니다.
  }
});

 

 

recoil로 로컬스토리지 제대로 사용하기

아래에 recoil로 로컬스토리지 사용법을 가볍게 만들어봤다.

만약 user라는 데이터가 전역에서 사용되고 있다면 루트컴포넌트에서 event 변경사항을 적용하면 된다.

외부 탭의 변경사항을 적용하고 싶지 않다면 storage 이벤트를 제거하면 된다.

 

예시 코드

import { useEffect } from 'react';
import { AtomEffect, atom } from 'recoil';

const localStorageEffect: <T>(key: string) => AtomEffect<T> =
  (key: string) =>
  ({ setSelf, onSet }) => {
    const savedValue = safeLocalStorage.get(key);
    if (savedValue) {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
      setSelf(JSON.parse(savedValue));
    }

    onSet((newValue, _, isReset) => {
      if (isReset) return safeLocalStorage.remove(key);

      return safeLocalStorage.set(key, JSON.stringify(newValue));
    });
  };

const LOCAL_STORAGE_KEY = {
  user: 'user',
};

export const userAtom = atom<null | User>({
  key: 'user',
  default: null,
  effects: [localStorageEffect('user')],
});

const MyComponent = () => {
  const [user, setUser] = useRecoilState(userAtom);

  useEffect(() => {
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === LOCAL_STORAGE_KEY.user) {
        const validateNewValue = validate(e.newValue);
        setUser(validateNewValue);
      }
    };

    window.addEventListener('storage', handleStorageChange);

    return () => {
      window.removeEventListener('storage', handleStorageChange);
    };
  }, [setUser]);

  // ... 컴포넌트의 나머지 부분 ...
};

export default MyComponent;

 

 

결론

웹 스토리지(로컬 스토리지)를 어떻게 사용해야 제대로 사용할지 정리해봤다. 사실 요구사항이 간단하다면 굳이 내가 마지막 단계에서 작성한 코드처럼 작성할 필요는 없다. 뭐든지 상황에 따라 적당히 사용하면 된다. 가장 중요한 것은 지금 겪고 있는 문제를 해결하고 앞으로 발생할 문제를 대비하는 것 아닐까?

728x90
LIST
댓글
공지사항