useSyncExternalStore

김병엽·2024년 8월 27일

useSyncExternalStore

  • 외부 저장소를 구독할 수 있는 React Hook.
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

Parameters

  • subscribe: 단일 콜백 인수를 받아서 스토어에 구독하는 함수. 스토어가 변경되면 제공된 콜백을 호출해야 한다. 그러면 컴포넌트가 다시 렌더링된다. subscribe는 구독을 정리하는(cleans up the subscription) 함수를 반환해야 한다.

  • getSnapshot: 컴포넌트에 필요한 스토어의 데이터 스냅샷을 반환하는 함수. 스토어가 변경되지 않은 동안 getSnapshot에 대한 반복적인 호출은 동일한 값을 반환해야 한다. 스토어가 변경되고 반환된 값이 다르면 React는 구성 요소를 다시 렌더링한다.

  • getServerSnapshot(optional): 스토어에 있는 데이터의 초기 스냅샷을 반환하는 함수. 서버 렌더링과 클라이언트에서 서버 렌더링된 콘텐츠의 하이드레이션 중에만 사용된다. 서버 스냅샷은 클라이언트와 서버 간에 동일해야 하며, 일반적으로 직렬화되어 서버에서 클라이언트로 전달된다. 이 인자를 생략하면 서버에서 구성 요소를 렌더링할 때 오류가 발생한다.


Returns

  • 렌더링 로직에 사용할 수 있는 스토어의 현재 스냅샷.

Caveats

  • getSnapshot에서 반환된 스토어 스냅샷은 변경 불가능해야 한다. 기본 스토어에 변경 가능한 데이터가 변경된 경우 새 변경 불가능한 스냅샷을 반환한다. 그렇지 않은 경우 캐시된 마지막 스냅샷을 반환한다.

  • 리렌더링 중에 다른 subscribe가 전달되면 React는 새로 전달된 구독 함수를 사용하여 스토어를 다시 subscribe합니다. 컴포넌트 외부에서 subscribe을 선언하면 이를 방지할 수 있다.

  • non-blocking Transition update 중에 스토어가 변형되면 React는 해당 업데이트를 blocking으로 수행한다. 구체적으로, 모든 전환 업데이트에 대해 React는 DOM에 변경 사항을 적용하기 직전에 getSnapshot을 두 번째로 호출한다. 원래 호출되었을 때와 다른 값을 반환하면 React는 업데이트를 처음부터 다시 시작하여 이번에는 blocking 업데이트로 적용하여 화면의 모든 구성 요소가 동일한 버전의 스토어를 반영하도록 한다.

  • useSyncExternalStore에서 반환된 스토어 값에 따라 렌더링을 일시 중단하는 것은 권장되지 않는다. 그 이유는 외부 스토어에 대한 변형은 non-blocking Transition update로 표시할 수 없기 때문에 가장 가까운 Suspense fallback(화면에 이미 렌더링된 콘텐츠를 로딩 스피너로 대체하는)을 트리거할 것이다.

const LazyProductDetailPage = lazy(() => import('./ProductDetailPage.js'));

function ShoppingApp() {
  const selectedProductId = useSyncExternalStore(...);

  // ❌ Calling `use` with a Promise dependent on `selectedProductId`
  const data = use(fetchItem(selectedProductId))

  // ❌ Conditionally rendering a lazy component based on `selectedProductId`
  return selectedProductId != null ? <LazyProductDetailPage /> : <FeaturedProducts />;
}

Usage

  • 대부분의 React 컴포넌트는 props, statecontext에서만 데이터를 읽는다.
  • 때때로 컴포넌트는 시간이 지남에 따라 변경되는 React 외부의 일부 저장소에서 일부 데이터를 읽어야 한다.(참고)
  • 컴포넌트의 최상위 수준에서 useSyncExternalStore를 호출하여 외부 데이터 저장소에서 값을 읽는다.

(참고)

React 외부에서 상태를 유지하는 상태 관리 라이브러리.
변경 가능한 값과 이벤트를 노출하여 변경 사항을 구독하는 Browers API.

import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';

function TodosApp() {
  const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
  // ...
}

React는 이러한 함수를 사용하여 컴포넌트를 스토어에 구독하고 변경 사항이 있을 때 다시 렌더링한다.


Extracting the logic to a custom Hook

  • 일반적으로 useSyncExternalStore를 컴포넌트에 직접 작성하지 않는다.
  • 대신 custom Hook에서 호출한다. 이렇게 하면 다른 컴포넌트에서 동일한 외부 저장소를 사용할 수 있다.
import { useSyncExternalStore } from 'react';

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

function getSnapshot() {
  // ...
}

function subscribe(callback) {
  // ...
}

Troubleshooting

“The result of getSnapshot should be cached”

  • 이 오류는 getSnapshot 함수가 호출될 때마다 새 객체를 반환한다는 것을 의미.
function getSnapshot() {
  // 🔴 Do not return always different objects from getSnapshot
  return {
    todos: myStore.todos
  };
}

getSnapshot 객체는 실제로 무언가가 변경된 경우에만 다른 객체를 반환해야 한다.

스토어에 변경 불가능한 데이터가 포함된 경우 해당 데이터를 직접 반환할 수 있다.

function getSnapshot() {
  // ✅ You can return immutable data
  return myStore.todos;
}

Conclusion

  • 가능하다면 대신 useStateuseReducer를 사용하여 내장된 React 상태를 사용하는 것이 좋다.
  • useSyncExternalStore는 기존 비 non-React 코드와 통합해야 하는 경우에 주로 유용하다.

Reference

React.doc

profile
선한 영향력을 줄 수 있는 개발자가 되자, 되고싶다.

2개의 댓글

comment-user-thumbnail
2024년 8월 30일

현기증 나요 다음 화 얼른 올려주세요!!

1개의 답글