본문 바로가기
About IT/웹 개발

ReactQuery로 브라우저 탭 간 데이터 동기화 해결하기

by yjin_fe 2024. 10. 22.

들어가며..

  개발을 하다 보면, 기획자로부터 크고 작은 다양한 기능에 대한 요구사항을 받게 됩니다. 때로는 어려워 보이는 기능이 쉽게 풀리는 경우도 있고, 쉬워 보이는 기능이 오히려 어렵게 개발되는 경우도 있습니다. 

  저는 이번 프로젝트에서 브라우저 탭 간 데이터 동기화에 관련한 기능을 구현해달라 요구받았습니다. 여러 개의 탭을 열고 작업하는 상황에서, 한 탭에서 수정한 내용이 다른 탭에서도 즉시 반영되어야 하는 것이었습니다. 거의 실시간 데이터 동기화 같은 느낌이라 여러가지 생각이 들었습니다만, 결과적으로 react-query의 유용한 옵션을 통해 문제를 해결했습니다. 이번 글에서는 문제를 어떤 방식으로 해결했는지 공유해보려 합니다.

 

 

문제는..

제가 마주했던 문제인 브라우저 탭 간 데이터 동기화에 관련한 기능은, 사실 요구 사항이 이렇게 표현되어 있지 않았습니다. 단지, 기획자가 전달해 준 요구사항 문서(기획서)에는 다음과 같은 요구사항이 있었습니다.

 

1. 사용자가 서비스를 A 탭에 열어놓고, 또 다른 탭(탭 B)을 열어서 서비스의 동일한 페이지를 봅니다.

2. 탭 B에서 어떤 데이터를 수정했을 때, 탭 A에도 수정된 데이터의 내용이 자동으로 반영되어야 합니다.

3. 중요한 점은 유저가 탭을 전환할 때마다 새로고침을 하지 않아도 최신 데이터가 보여져야 합니다.

 

처음 떠오른 해결 방법들..

이 요구사항을 듣고 첫 생각은 '음... 이건 거의 실시간 업데이트인데..? 쉽지 않겠군... 어떤 방식을 써야 하나...?' 하는 생각이었습니다. (사실 더 먼저 든 생각은 '굳이... 이런 기능이 필요한가..?'였습니다만..) 잠시 동안 어떻게 이 요구사항을 구현할 지 생각해보았습니다. 떠올린 방법들은 대부분 서버와의 통신을 통해 실시간으로 데이터를 업데이트하는 폴링, Sever-Sent-Event, 웹 소켓 등의 방법이었습니다. 이 방법들은 각각 장단점이 명확했고, 과연 정말 이 문제를 해결하기에 적합한 방법인지 간단히 장단점을 정리를 해보았습니다.

 

폴링

일정한(정해진) 시간 간격마다 서버에 데이터를 요청하여 갱신하는 방식

[장점]

  • 간단한 구현: 클라이언트에서 정해진 시간 간격마다 요청만 하면 되므로 복잡한 설정이 필요하지 않음.

  • 서버 부하 조절 가능: 요청 빈도를 클라이언트에서 제어할 수 있으므로 서버에 과도한 부하를 주지 않도록 조정 가능.

[단점]

  • 비효율적 통신: 데이터가 변경되지 않은 상황에서도 주기적으로 요청을 보내기 때문에 불필요한 네트워크 트래픽이 발생.

  • 실시간성 부족: 요청 간격에 따라 데이터 갱신에 시간 차가 발생할 수 있어, 엄밀히 말하면 실시간 데이터 동기화가 아님.

 

Server Sent Event (SSE)

서버가 클라이언트에게 실시간으로 변경 사항 데이터를 푸시하는 방식

[장점]

  • 단방향으로 실시간 업데이트 가능: 서버에서 변경 사항이 생길 때마다 데이터를 보내므로 실시간 반응성을 제공.

  • HTTP 프로토콜 사용: 기존 HTTP 기반 환경에서 쉽게 적용 가능하고, 방화벽 등의 제약을 적게 받음.

[단점]

  • 브라우저 호환성 문제: 일부 오래된 브라우저에서는 완벽히 지원되지 않을 수 있음.

  • 양방향 통신 불가능: 클라이언트가 서버로 데이터를 전송하는 기능은 없으며, 일방적으로 서버에서 클라이언트로 데이터를 푸시하는 구조임.

 

웹 소켓

양방향 통신으로 지속적인 연결을 통해 실시간 업데이트를 제공하는 방식.

[장점]

  • 실시간 양방향 통신: 클라이언트와 서버 모두 실시간으로 데이터를 주고받을 수 있으며, 지연 시간이 거의 없음.

  • 효율적: 한 번 연결이 이루어지면 지속적인 데이터 전송이 가능하여, 매번 새롭게 연결을 맺을 필요가 없으므로 효율적.

[단점]

  • 구현의 복잡성: 다른 방식에 비해 구현이 복잡하며, 서버와 클라이언트 모두 웹 소켓 연결을 지원하는 구조로 만들어야 함.

  • 연결 유지 부담: 지속적인 연결을 유지해야 하므로 서버 자원을 더 많이 사용, 불안정한 네트워크 환경에서는 연결 유지가 어려울 수 있음.

 

 

선택한 해결 방법은..

결과적으로 위 3가지 중 어떤 방법도 선택하지 않았습니다. 지금 상황에 어울리지 않는다고 생각했기 때문입니다. 좀 더 구체적으로는, 개발 기간이 충분하지 않았고, 서버의 도움 없이 프론트엔드만의 리소스를 투자해서 만들었어야 했기 때문입니다. 그러다 문득 생각난 것이 React Query의 옵션, refetchOnWindowFocus였습니다. React Query에서 제공하는 refetchOnWindowFocus 옵션을 사용하면, 탭이나 윈도우가 다시 포커스를 받을 때마다 쿼리를 자동으로 재요청하여 최신 데이터를 가져올 수 있게 됩니다. 이를 통해 브라우저 탭 간의 전환 시 데이터의 최신 상태를 항상 유지할 수 있었습니다.

 

공식 문서에서는 요렇게 간단하게 언급하고 있습니다.

https://tanstack.com/query/latest/docs/framework/react/guides/window-focus-refetching

 

TanStack | High Quality Open-Source Software for Web Developers

Headless, type-safe, powerful utilities for complex workflows like Data Management, Data Visualization, Charts, Tables, and UI Components.

tanstack.com

실제 사용 방식 또한 매우 간단합니다. 옵션으로 boolean 값만 조절해주면 됩니다.

const { data } = useQuery('fetchData', fetchDataFunction, {
  refetchOnWindowFocus: true, // 윈도우 포커스 시 자동으로 데이터를 다시 가져옴
});

이렇게 간단하게 설정만 추가하면, 탭 전환 시 최신 데이터를 자동으로 보여줄 수 있었습니다.

 

 

잘 동작하는데.. But, Why?..

그렇다면, refetchOnWindowFocus를 사용하면 탭 전환 시 데이터가 동기화될까요? 그 뒤에는 Document Event 중 하나인 visibilitychange가 작동하고 있었습니다.

 

Document Event: visibilitychange

visibilitychange 이벤트는 문서(페이지)가 숨겨지거나(탭이 비활성화됨), 다시 표시될 때(탭이 활성화됨) 발생하는 이벤트입니다. 이를 통해 브라우저가 언제 포커스되었는지 감지할 수 있습니다. 이 이벤트는 document.visibilityState 속성과 함께 동작합니다. 이 속성은 페이지가 보이는 상태인지, 숨겨진 상태인지를 나타내며 두 가지 값 중 하나를 가집니다:

  • visible: 페이지가 사용자에게 보이는 상태
  • hidden: 페이지가 숨겨져 있는 상태

React Query의 refetchOnWindowFocus는 이 visibilitychange 이벤트를 기반으로 문서(페이지)의 포커스 변화를 감지하여, 포커스가 다시 돌아왔을 때 데이터를 다시 요청하여 최신 데이터를 가져오게 됩니다. 만약 현재 React Query를 사용하고 있지 않다고 좌절하고 계시다면, 다음과 같이 직접 이벤트 리스너를 붙여서 사용할 수 있으니 좌절하지 않으셔도 괜찮습니다.

 

visibilitychange 이벤트의 사용 예시

document.addEventListener('visibilitychange', function() {
  if (document.visibilityState === 'visible') {
    // 페이지가 다시 보일 때 수행할 동작
    fetchData();
  }
});

이와 같은 방식으로, 페이지가 다시 보이기 시작할 때 데이터를 자동으로 다시 가져오도록 설정할 수 있습니다.

 

 

이 문제를 해결하며 얻었던 것들..

기술적 성장

이 문제를 해결하면서 단순히 React Query의 refetchOnWindowFocus 옵션을 적용하는 것으로 끝나는 것이 아니라, 그 이면에 있는 기술적인 원리까지 이해할 수 있었습니다. 특히 visibilitychange 이벤트의 개념, 동작 방식과 그 활용 가능성을 알게 되었고, 앞으로도 비슷한 요구사항이 있을 때 더 나은 해결책을 제시할 수 있을 것 같다는 생각을 했습니다. 또한, 프론트엔드 생태계에는 React Query와 같은 편리한 기능을 제공하는 외부 라이브러리들이 상당히 많이 있습니다. 라이브러리들이 내부적으로 어떤 기술을 사용해서 개발자에게 편리한 기능을 제공하는지 알아보는 과정이 꽤나 흥미로웠습니다. 앞으로는 다른 라이브러리들도 어떤 기술을 활용해 구현되었는지 알아보는 습관을 기르고자 합니다.

 

협업의 가치

이후에도 비슷한 문제를 마주한 동료 개발자에게 제가 선택했던 방법을 제시해줄 수 있었습니다. 한 백엔드 개발자 동료분이 기획서를 토대로 기능을 구현해야 하는데, SSE를 써야할 것 같다는 이야기를 들었습니다. 어떤 요구사항을 만족하려 해당 기술을 사용하려 하는 지 여쭤보았습니다. 이야기를 들어보니, 제가 마주했던 문제와 비슷한 종류였습니다. 제 경험으로 react-query의 옵션을 사용하면, 백엔드 공수 없이 프론트엔드의 코드만으로 해결이 가능할 것 같다고 제안드렸고, 보다 쉽게 해결할 수 있게 되었다는 피드백을 받을 수 있었습니다.

 

 

마무리..

아마 이 글을 보시면서 제가 선택한 방법이 꼭 요구사항에 대한 완벽한 해결책이 아니라고 생각하시는 분들이 있으실 수 있습니다. 저도 그러한 의견에 일부 동의하는데요, 왜냐하면 API의 요청 시 데이터가 많거나, 비싼 요청의 경우라면 자주 요청하는 것이 자원의 낭비가 될 수 있기 때문입니다. 그리고 성능 문제, 리렌더링 등 사실 더 많은 이슈가 발생할 수 있습니다. 즉, 모든 상황에 적용되기 어려울 수 있습니다. 다만, 한정된 조건 아래에서 빠르게 위와 같은 요구사항을 해결하기엔 적합한 방법이었다고 생각합니다. 물론 더 좋은 방법을 알게 된다면 바꿔야 할 것입니다. (변경하게 된다면 그에 대한 글을 추가로 적어볼 예정입니다.)

 

이외 추가적인 질문이나 잘못된 내용이 있다면 댓글 혹은 메일로 피드백 부탁드립니다! 😊

 

참고자료

https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event

https://tanstack.com/query/latest/docs/framework/react/guides/window-focus-refetching

https://velog.io/@dev_jazziron/Polling-Long-Polling-SSE-WebSocket