[React] useSyncExternalStore

리액트에서 외부 저장소를 구독하기 위한 훅이다!

useEffect에대한 발표를 준비하며 나왔는데 Effect 대신 권장하는 방법으로 이 훅을 알려줬는데.. 예제가 이해되지 않아 찾아보았다.

 

useSyncExternalStore 란?

리액트에서 외부 데이터 저장소를 구독하고, 컴포넌트에 저장소의 값을 읽어올 수 있도록 하는 훅

외부 데이터가 변경되면 컴포넌트를 리렌더링하여 변경사항을 반영할 수 있음

 

 

사용법

기본 구조

const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);

 

인자

1) subscribe: 외부 저장소에 구독하기 위해 필요한 동작을 담을 함수, 구독을 정리하는 함수를 반환해야 함

2) getSnapshot: 저장소의 현재 값을 반환할 함수, 저장소가 변경되어 변환된 값이 다를 때 컴포넌트를 리렌더링

3) getServerSnapshot: 서버 렌더링시 초기값을 제공하는 함수 (optional)

 

반환값

저장소의 현재 스냅샷 (데이터 값)

 

 

주요 사용 사례

1) 브라우저 API와 통합 (ex. 온라인 상태, 윈도우 크기 등)

2) 타사 상태 관리 라이브러리와 통합

 

 

장점

1) 외부 데이터와 React 상태를 쉽게 동기화할 수 있음

2) 불필요한 리렌더링을 방지해 성능 최적화

3) 동시성 기능과의 호환성

 

 

예제

✔︎ useSyncExternalStore를 이용해 브라우저 API - 윈도우 크기와 동기화하기

 

import { useSyncExternalStore } from 'react';

function useWindowSize() {
  const subscribe = (callback) => {
    window.addEventListener('resize', callback);
    return () => window.removeEventListener('resize', callback);
  };

  const getSnapshot = () => {
    return { width: window.innerWidth, height: window.innerHeight };
  };

  const size = useSyncExternalStore(subscribe, getSnapshot);

  return size;
}

function WindowSizeDisplay() {
  const { width, height } = useWindowSize();

  return (
    <div>
      <p>현재 윈도우 크기:</p>
      <p>너비: {width}px, 높이: {height}px</p>
    </div>
  );
}

- useWindowSize: 커스텀 훅, useSyncExternalStore를 이용해 윈도우 크기를 받아옴

- subscribe: window의 resize 이벤트에 이벤트리스너 callback를 추가 / 제거

- subscribe의 인자 callback: resize 이벤트가 일어날 때 호출

- getSnapshot: 윈도우 사이즈를 받아오기 위해 필요한 함수, { width, heigth }를 반환함

- 윈도우에서 resize 이벤트가 일어날 때마다 callback이 호출되면서 새로 받아온 데이터로 컴포넌트가 리렌더링됨

 

-> 이 방식을 이용한 반응형 웹 레이아웃을 만드는 것은 권장되지 않으며, 기본적으로 css 미디어쿼리를 이용하고 일부 윈도우 크기를 이용해야하는 로직이나 윈도우 크기에 따른 동적 컨텐츠 로딩 등에 사용할 수 있다!

 

 

유의사항

error: 'getSnapshot'의 결과를 캐시해야 합니다.

getSnapshot 함수가 호출될 때마다 새로운 객체를 반환하는 경우에 발생

function getSnapshot() {
  return {
    todos: myStore.todos
  };
}

- 이 경우 실제 외부 데이터는 변경되지 않더라도 매 호출마다 새 주소값을 담은 객체가 반환되므로, 매번 새로운 값이라고 인지

- 이러한 방식은 무한 루프로 에러를 발생시킴

 

리렌더링마다 subscribe가 호출되는 경우

function ChatIndicator() {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot);
  
  function subscribe() {
    // ...
  }

  // ...
}

- ChatIndicator 컴포넌트가 리렌더링될 때마다 참조타입인 subscribe는 새로운 주소에 할당됨

- useSyncExternalStore 입장에서는 구독에 필요한 동작이 달라졌으므로, 리렌더링마다 다시 구독을 실행

 

function ChatIndicator() {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot);
  // ...
}

function subscribe() {
  // ...
}

- 이렇게 컴포넌트 외부로 subscribe를 빼주면 계속 새로운 참조를 갖고있으므로, 새로 구독을 실행하지 않음

 

function ChatIndicator({ userId }) {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot);

  const subscribe = useCallback(() => {
    // ...
  }, [userId]);

  // ...
}

- 특정 경우에 외부 저장소를 새로 구독하고 싶은 경우 위와 같이 useCallback을 사용해 subscribe를 선언해줄 수 있음

- 위 예제는 userId가 변경될 때 새로운 주소를 갖는 함수를 반환하므로, userId가 변경될 때마다 새로 외부 저장소를 구독하는 동작을 만들 수 있음

'React' 카테고리의 다른 글

[React] react-router-dom  (0) 2024.08.20
[React] Effect가 필요하지 않을 수도 있습니다  (0) 2024.08.18
[React] memoization  (0) 2024.08.17
[React] useContext  (0) 2024.08.16
[React] useRef  (0) 2024.08.16