React 18 useDeferredValue 심화: 검색 자동완성에서 디바운싱을 제거하는 이유

React 18 useDeferredValue 심화: 검색 자동완성에서 디바운싱을 제거하는 이유

React 18 핵심: useSyncExternalStore 훅 완벽 가이드

React 18 핵심: useSyncExternalStore 훅 완벽 가이드

React 개발자라면 누구나 한 번쯤 전역 상태 관리 라이브러리를 사용하며 고민에 빠진 적이 있을 것입니다.
Redux, Zustand, Recoil 등 수많은 라이브러리가 존재하지만, React 18 버전이 출시되면서 우리는 새로운 도전에 직면하게 되었습니다.
바로 동시성 렌더링(Concurrent Rendering)의 등장입니다.

"왜 내 앱의 UI가 순간적으로 일치하지 않지?"라는 의문을 가져본 적이 있으신가요?
이것은 React가 렌더링을 멈추고 다른 급한 작업을 처리하는 동안, 외부 데이터가 변경되어 발생하는 테어링(Tearing) 현상일 수 있습니다.
오늘은 이 문제를 해결하기 위해 React 팀이 야심 차게 내놓은 무기, useSyncExternalStore에 대해 깊이 있게 파헤쳐 보겠습니다.

🚀 이 글을 통해 얻어갈 수 있는 것:

  • React 18의 동시성 기능과 테어링(Tearing) 문제의 본질적 이해
  • useEffect와의 결정적인 차이점 분석
  • 외부 스토어(Store)를 React와 안전하게 동기화하는 실전 패턴

🧐 도대체 '테어링(Tearing)'이 무엇인가요?

본격적인 훅 설명에 앞서, 우리가 해결해야 할 '적'을 먼저 식별해야 합니다.
React 18 이전의 동기적 렌더링에서는 렌더링이 시작되면 멈추지 않고 끝까지 진행되었습니다.
하지만 동시성 렌더링에서는 React가 렌더링 도중 잠시 멈추고, 더 높은 우선순위의 작업(예: 클릭 이벤트)을 처리할 수 있게 되었습니다.

문제는 여기서 발생합니다.
컴포넌트 트리의 일부는 변경 전의 데이터(Red)로 렌더링되었는데, 렌더링이 일시 중지된 사이 외부 스토어의 값이 변경(Blue)되어 버립니다.
그 후 나머지 컴포넌트가 렌더링 될 때는 변경된 데이터(Blue)를 참조하게 됩니다.
결국 사용자는 한 화면에서 서로 다른 두 가지 상태의 UI를 동시에 목격하게 되는데, 이를 화면이 찢어졌다는 의미로 테어링(Tearing)이라고 부릅니다.

⚠️ 핵심 요약:
테어링은 렌더링 프로세스 도중에 외부 상태가 변경되어, UI의 일관성이 깨지는 시각적 결함입니다.


🛡️ useEffect로는 부족한 이유와 해결책

"그냥 useEffect로 상태 변경을 구독하면 되지 않나요?"라고 반문하실 수 있습니다.
하지만 useEffectuseLayoutEffect는 렌더링이 완료된 '이후'에 실행되거나 커밋 단계에서 실행됩니다.
동시성 모드에서는 이것만으로는 렌더링 도중 발생하는 상태 불일치를 완벽하게 막을 수 없습니다.

📊 useEffect vs useSyncExternalStore 비교

구분 useEffect + useState useSyncExternalStore
렌더링 타이밍 렌더링 후 비동기 실행 렌더링 과정 중 동기적 실행
동시성 지원 테어링 발생 가능성 있음 테어링 완벽 방지
주 사용처 일반적인 사이드 이펙트 전역 상태 라이브러리, 브라우저 API 구독

useSyncExternalStore는 이름 그대로 외부 저장소(External Store)를 React 렌더링과 동기화(Sync) 시키는 훅입니다.
이 훅을 사용하면 React는 스토어의 변경 사항이 있는지 렌더링 도중에도 지속적으로 확인하며, 변경이 감지되면 즉시 동기적으로 다시 렌더링을 수행하여 UI의 일관성을 강제합니다.


💻 실전 구현: 코드로 이해하기

이제 실제 코드를 통해 사용법을 알아보겠습니다.
이 훅은 크게 세 가지 인자를 받습니다.

const state = useSyncExternalStore(
  subscribe, // 스토어 변경을 감지할 구독 함수
  getSnapshot, // 현재 스토어 상태를 반환하는 함수
  getServerSnapshot? // (옵션) SSR을 위한 초기 상태 함수
);

🛠️ 브라우저의 'online' 상태 추적하기

가장 쉬운 예시로, 사용자의 네트워크 상태(온라인/오프라인)를 추적하는 커스텀 훅을 만들어 보겠습니다.
브라우저의 API는 React 외부의 데이터이므로 이 훅을 사용하기에 아주 적절한 대상입니다.

Step 1: subscribe 함수 정의

스토어가 변경될 때 호출될 콜백을 등록하고, 구독을 해제하는 클린업 함수를 반환해야 합니다.
여기서는 window 객체의 이벤트를 구독합니다.

Step 2: getSnapshot 함수 정의

현재의 데이터를 반환합니다.
중요: 스토어가 변하지 않았다면 항상 동일한 값을 반환해야 불필요한 리렌더링을 막을 수 있습니다.

function useOnlineStatus() {
  const isOnline = useSyncExternalStore(
    subscribe,
    getSnapshot
  );
  return isOnline;
}

function getSnapshot() {
  return navigator.onLine;
}

function subscribe(callback) {
  window.addEventListener('online', callback);
  window.addEventListener('offline', callback);
  return () => {
    window.removeEventListener('online', callback);
    window.removeEventListener('offline', callback);
  };
}

위 코드를 사용하면 React는 네트워크 상태가 변경되는 즉시 컴포넌트를 동기화하여, 렌더링 도중 상태가 꼬이는 것을 방지합니다.
라이브러리 제작자가 아니더라도, 이러한 패턴을 익혀두면 window.resize, matchMedia 등 다양한 브라우저 API를 안전하게 연동할 수 있습니다.


🏁 더 견고한 앱을 위하여

React 18의 useSyncExternalStore는 단순한 API 추가가 아닌, 동시성 시대를 맞이하는 React의 철학을 보여주는 기능입니다.
물론 일반적인 앱 개발에서 애플리케이션 상태(Props, State)만으로 충분한 경우에는 굳이 이 훅을 사용할 필요는 없습니다.
하지만 전역 상태 라이브러리를 직접 구현하거나, 복잡한 외부 데이터 소스를 다뤄야 한다면 필수적으로 고려해야 할 도구입니다.

✅ Action Plan: 오늘 바로 적용해보기

  • 현재 프로젝트에서 useEffectwindow 이벤트를 구독하고 있는 코드를 찾아보세요.
  • 해당 코드를 useSyncExternalStore를 사용하는 커스텀 훅으로 리팩토링 해보세요.
  • React 개발자 도구의 'Concurrent Mode' 시뮬레이션을 켜고 UI가 찢어지는 현상이 없는지 확인해 보세요.

댓글 쓰기