본문 바로가기

Study

useSyncExternalStore로 외부 상태 안전하게 구독하기

 

주제 선정 이유

 

최근 이력서를 넣은 회사에서 사전과제를 진행했는데 처음 사용하는 기술이 있어 그에 대해 좀 더 공부하고자 포스팅을 했다.

과제 내용은 상태관리 라이브러리를 구현해보는 것이었는데 이때 외부 store와 연결을 위해 useSyncExternalStore 라는 함수를 사용했다.

 

동시성 렌더링 (Concurrent Rendering)

 

useSyncExternalStore를 이해하려면 먼저 Concurrent Rendering(동시성 렌더링) 개념을 이해할 필요가 있다.
이 개념은 React 18에서 도입된 것으로 UI 렌더링 작업을 보다 유연하고 끊김 없이 처리해 앱의 성능과 사용자 경험(UX)을 개선하는 것을 목표로 한다. 예를 들어 입력창에 텍스트를 입력할 때 입력한 값을 실시간으로 화면에 렌더링하는 경우를 생각해보자. 이 과정에서 렌더링 작업이 무거우면 입력이 버벅이거나 렌더링 중에 새로운 입력이 들어와도 즉시 반응해야 하므로 렌더링을 멈출 수 없다.
이처럼 사용자의 인터랙션과 렌더링 작업이 동시에 일어나는 상황에서 기존의 동기 렌더링 방식으로는 부드러운 경험을 제공하기 어렵다.

 

React의 동시성 렌더링은 이런 문제를 해결하기 위해 렌더링 작업을 더 작은 단위로 나누고 중간에 멈췄다가 다시 이어서 실행할 수 있는 방식을 사용한다. 우선순위가 높은 작업은 먼저 처리하고 복잡한 UI 갱신 작업은 백그라운드에서 따로 준비해두었다가 적절한 시점에 반영할 수 있다. 예를 들어 사용자가 입력창에 타이핑하는 도중에도 React는 이전 렌더링 작업을 잠시 멈추고 가장 최신 입력값을 기반으로 렌더링을 다시 시작할 수 있다. 즉, 이전 작업을 억지로 끝내는 것이 아니라,필요하다면 중단하고 최신 작업으로 대체하는 방식으로 동작한다. 이런 메커니즘 덕분에 무거운 컴포넌트를 렌더링하거나 많은 데이터가 오가는 상황에서도 UI는 버벅임 없이 부드럽게 동작할 수 있게 된다.

 

그렇다면 이런 환경에서 외부 상태를 React와 안정적으로 연결하려면 어떻게 해야 할까?
바로 이 지점을 해결하기 위해 등장한 것이 useSyncExternalStore이다.

 

useSyncExternalStore

 

기존에는 외부 상태를 컴포넌트에서 직접 구독하고 값이 바뀔 때마다 함수를 호출해 동기화하는 방식이 일반적이었다. 하지만 Concurrent Rendering 환경에서는 이 방식이 렌더링 타이밍과 값이 어긋나는 문제를 발생시킬 수 있다. useSyncExternalStore는 이러한 문제를 해결하기 위해 React 18에서 새롭게 도입된 훅으로 외부 스토어의 상태를 React 렌더링 흐름과 안전하게 동기화할 수 있도록 돕는다.

 

사용 방법
const snapshot = useSyncExternalStore(
  subscribe, // 값 변경 감지
  getSnapshot, // 현재 값을 동기적으로 반환
  getServerSnapshot // (optional) SSR 대응 시 사용
);

 

 

 

인자 설명
subscribe 외부 상태 변화 감지를 위해 구독 콜백을 등록하는 함수
구독 해제 함수를 반환값에 넣으면 컴포넌트가 언마운트시 동작함
getSnapshot 현재의 상태를 동기적으로 반환
렌더 직전에 호출되어 React와 상태를 일치시킴
getServerSnapshot 서버 사이드 렌더링(SSR) 시 초기 값을 주기 위해 사용 (optional)

 

예시

 

아래는 실제 과제에서 작성한 코드로 useSyncExternalStore를 이용해 외부 상태를 React 컴포넌트 안에서 안전하게 구독하고 갱신할 수 있도록 만든 커스텀 훅이다.

import { useCallback, useSyncExternalStore } from "react";
import { Atom, get, set, subscribe } from "@/lib/atom";

export function useAtom<T>(atom: Atom<T>): [T, (val: T) => void] {
  const store = useSyncExternalStore(
    useCallback(
      (onStoreChange: () => void) => {
        const unsubscribe = subscribe(atom, () => {
          onStoreChange();
        });
        return unsubscribe;
      },
      [atom]
    ),
    useCallback(() => get(atom), [atom])
  );

  const setter = useCallback((val: T) => set(atom, val), [atom]);

  return [store, setter];
}

 

  • subscribe(atom, callback)는 상태(atom)가 변경되었을 때 callback을 호출
  • React는 onStoreChange가 호출되면 getSnapshot 부분의 함수를 다시 호출해 최신 값을 가져오고 컴포넌트를 리렌더링
  • unsubscribe는 컴포넌트가 언마운트 될 때 외부 상태 구독을 해제해주는 함수로 메모리 누수와 불필요한 업데이트를 방지

 

 

 

참고
 

useSyncExternalStore – React

The library for web and native user interfaces

ko.react.dev

 

'Study' 카테고리의 다른 글

Sorting 알고리즘  (0) 2025.07.12
Cypress로 테스트 코드 작성하기  (0) 2025.04.15
브라우저 렌더링 (Browser rendering)  (0) 2024.07.01