웹 브라우저 팝업과 데이터 동기화 with React

건둔덕 ·2024년 5월 23일
1

React

목록 보기
2/2
post-thumbnail

🗣️ 시작하기 전에

해가 쨍쨍한 어느 날, 어김없이 모닝 커피와 함께 하루를 시작했다.
갑자기 회의가 잡히더니 기존에 운영중이였던 서비스 중 하나를 재개발을 하는게 낫겠다 싶을 정도의 요구 사항이 들어왔다...

어쩐지 그 날따라 커피가 쓰더라...

데이터 구조 자체를 바꿔야하는 요구사항도 있어서 백엔드도 막막한 상황이였다.

그래서 🌟 재개발 🌟을 그냥 진행하기로 했다!

어차피 기존 서비스가 Vue.js로 만들어져 있었고, 심지어 업데이트가 중지된 버전 2로 되어있었다. 😅

웹 브라우저 팝업과 데이터 동기화 라는 제목은 기존 서비스를 재개발 하면서 겪은 수많은 이슈들 중 하나이다.

🛫 시작하기

재개발을 하면서 기존의 기능 + 추가 요구사항 때문에
메인으로 제일 많이 쓰이는 페이지는 기능이 굉장히 많았다.

💡 Soket 포함 32개의 API가 한 페이지에서 쓰였다. (직접 세어봤습니다)

디자이너가 다른 프로젝트 때문에 바빠서 직접 와이어 프레임을 잡아가며 혼자 작업을 하게 되었는데, 한 페이지에 기능이 너무 많아 고민을 많이 했었고 후에 서비스가 좀 더 확장되었을 때도 고려를 해야했다.

일단 제목에서 알 수 있듯 해당 페이지에서는 새 브라우저 팝업을 띄워서 작업을 해야했고, 새로 띄운 브라우저와 메인 브라우저의 데이터가 동기화 되어야 했다.

처음에는 어렵게 생각하지 않고 이렇게 생각했었던 것 같다.

"그냥 전역 상태 관리하는 라이브러리 사용하면 되겠다"


데이터 동기화 과정

전역 상태 관리 라이브러리는 zustand를 사용했는데, 새 브라우저 팝업을 열어서 사용하니 전역 상태 데이터들이 전혀 공유가 되지 않았다.

🛠️ LocalStorage & CustomHook

전역 상태 관리 라이브러리로의 일반적인 사용 방법으로는 사용이 불가 해서 zustandpersist를 사용해서 localStorage에 데이터를 저장해서 데이터를 공유했다.

근데 localStorage는 데이터가 변경 되는게 확인이 되는데, zustand의 기능으로는 각 브라우저가 전역 상태를 변경 했을 때의 이벤트를 감지하지 못했다.

상태가 변하는 이벤트를 감지 못하니 당연하게도 데이터를 서로 동기화하지 못하는 상황이 발생했다.

하지만 localStorage는 변경이 되고 있기 때문에 직접 이벤트 리스너를 만들었다.

 const useSyncWithPersistStore = (key: String) => {
  const updateStore = () => {
    useQaWorkStore.persist.rehydrate();
  };

  useEffect(() => {
    const updateQaWorkStore = (event: StorageEvent) => {
      if (event.key === key) updateStore();
    };

    window.addEventListener('storage', updateQaWorkStore);

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

커스텀 훅으로 만든 뒤, 메인 브라우저의 해당 페이지와 팝업 브라우저에 각 한 개씩 선언해줬다.

⛔️ 이슈

하지만 위의 커스텀 훅을 사용할 때 문제점이 두 가지 있었다.

첫 번째 문제는 위 커스텀 훅의 목적은 포커싱이 되어있는 브라우저에서 데이터를 변경할 때, 포커싱이 되어 있지 않은 브라우저가 변경 이벤트를 감지한 후 데이터를 동기화 시켜줄 수 있도록 만들어 주는 것인데, 포커싱이 되어 있는 곳이 간헐적으로 커스텀 훅의 이벤트가 적용될 때가 있었다.

두 번째 문제는 포커싱된 브라우저에서 데이터를 변경하는 이벤트를 빠르게 여러 번 일으키게 되면, 버벅거림 현상이 자주 일어나 UX 적으로 매우 좋지 않았다.

✅ 해결

첫 번째 문제는 커스텀 훅 안의 이벤트 리스너가 작동할 때 실행되는 함수가 실행되서 rehydrate 처리될 때, document.hasFocus() 메서드를 활용해서 사용자가 동작하고 있지 않은 브라우저에서만 함수가 작동하도록 제한을 두었다.

두 번째 문제는 Debounce 처리로 해결을 했다.
Debounce란 특정 이벤트가 연속적으로 발생할 때, 마지막 이벤트가 발생하고 일정 시간 동안 더 이상 동일한 이벤트가 발생하지 않을 때까지 해당 이벤트 처리 함수를 지연시키는 방법이다.

Debounce 이벤트는 검색할 때의 keyboard 이벤트나 스크롤 이벤트 또는 브라우저 창 사이즈를 변경하는 이벤트에 주로 사용하는데, 나는 팝업 브라우저와 메인 브라우저 사이의 데이터 동기화 과정에서 발생하는 UX 문제의 해결 방법으로 활용을 했다.

아래는 해결한 이후의 코드이다.


const useSyncWithPersistStore = (key: string, time?: number) => {
  let debounceTimer: NodeJS.Timeout | null = null;

  const updateStore = () => {
    if (!document.hasFocus()) useQaWorkStore.persist.rehydrate();
  };

  useEffect(() => {
    const updateQaWorkStore = (event: StorageEvent) => {
      if (event.key === key) {
        if (debounceTimer) clearTimeout(debounceTimer);

        debounceTimer = setTimeout(() => {
          updateStore();
        }, time || 250);
      }
    };

    window.addEventListener('storage', updateQaWorkStore);

    return () => {
      window.removeEventListener('storage', updateQaWorkStore);
      if (debounceTimer) clearTimeout(debounceTimer);
    };
  }, []);
};

다른 의견이나 잘못된 부분이 있다면 댓글 부탁드립니다!

profile
건데브

0개의 댓글