useSyncExternalStore
is a React Hook that lets you subscribe to an external store.
useSyncExternalStore
는 외부 스토어를 구독할 수 있는 React 훅입니다.
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Reference참조
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Call useSyncExternalStore
at the top level of your component to read a value from an external data store.
컴포넌트의 최상위 레벨에서 useSyncExternalStore
를 호출하여 외부 데이터 저장소에서 값을 읽습니다.
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
// ...
}
It returns the snapshot of the data in the store. You need to pass two functions as arguments: 스토어에 있는 데이터의 스냅샷을 반환합니다. 두 개의 함수를 인수로 전달해야 합니다:
-
The
subscribe
function should subscribe to the store and return a function that unsubscribes.subscribe
함수는 스토어를 구독해야 하고, 구독 취소 함수를 반환해야 합니다. -
The
getSnapshot
function should read a snapshot of the data from the store.getSnapshot
함수는 스토어에서 데이터의 스냅샷을 읽어야 합니다.
See more examples below. 아래에서 더 많은 예시를 확인하세요.
Parameters매개변수
-
subscribe
: A function that takes a singlecallback
argument and subscribes it to the store. When the store changes, it should invoke the providedcallback
. This will cause the component to re-render. Thesubscribe
function should return a function that cleans up the subscription.subscribe
: 하나의callback
인수를 받아 스토어를 구독하는 함수입니다. 스토어가 변경되면 제공된callback
을 호출해야 합니다. 이로부터 컴포넌트가 리렌더링 됩니다.subscribe
함수는 구독을 해제하는 함수를 반환해야 합니다. -
getSnapshot
: A function that returns a snapshot of the data in the store that’s needed by the component. While the store has not changed, repeated calls togetSnapshot
must return the same value. If the store changes and the returned value is different (as compared byObject.is
), React re-renders the component.getSnapshot
: 컴포넌트에 필요한 스토어 데이터의 스냅샷을 반환하는 함수입니다. 스토어가 변경되지 않은 상태에서getSnapshot
을 반복적으로 호출하면 동일한 값을 반환해야 합니다. 저장소가 변경되어 반환된 값이 (Object.is
비교시) 달라지면, React는 컴포넌트를 리렌더링 합니다. -
optional
getServerSnapshot
: A function that returns the initial snapshot of the data in the store. It will be used only during server rendering and during hydration of server-rendered content on the client. The server snapshot must be the same between the client and the server, and is usually serialized and passed from the server to the client. If you omit this argument, rendering the component on the server will throw an error. 선택적getServerSnapshot
: 스토어에 있는 데이터의 초기 스냅샷을 반환하는 함수입니다. 오직 서버에서 렌더링할 때와 이를 클라이언트에서 hydrate하는 동안에만 사용됩니다. 서버 스냅샷은 클라이언트와 서버 간에 동일해야 하며, 일반적으로 서버에서 직렬화하여 클라이언트로 전달합니다. 이 함수가 제공되지 않으면 서버에서 컴포넌트를 렌더링할 때 오류가 발생합니다.
Returns반환값
The current snapshot of the store which you can use in your rendering logic. 렌더링 로직에 사용할 수 있는 스토어의 현재 스냅샷입니다.
Caveats주의사항
-
The store snapshot returned by
getSnapshot
must be immutable. If the underlying store has mutable data, return a new immutable snapshot if the data has changed. Otherwise, return a cached last snapshot.getSnapshot
이 반환하는 스토어 스냅샷은 불변이어야 합니다. 기본 스토어에 변이 가능한 데이터가 있는 경우에는, 데이터가 변이되면 새로운 불변 스냅샷을 반환하도록 하고, 변이 사항이 없으면 캐시된 마지막 스냅샷을 반환하도록 하세요. -
If a different
subscribe
function is passed during a re-render, React will re-subscribe to the store using the newly passedsubscribe
function. You can prevent this by declaringsubscribe
outside the component. 리렌더링시에 다른subscribe
함수가 전달되면 React는 새로 전달된subscribe
함수를 사용하여 저장소를 다시 구독합니다. 컴포넌트 외부에서subscribe
를 선언하면 이를 방지할 수 있습니다. -
If the store is mutated during a non-blocking Transition update, React will fall back to performing that update as blocking. Specifically, for every Transition update, React will call
getSnapshot
a second time just before applying changes to the DOM. If it returns a different value than when it was called originally, React will restart the update from scratch, this time applying it as a blocking update, to ensure that every component on screen is reflecting the same version of the store. -
It’s not recommended to suspend a render based on a store value returned by
useSyncExternalStore
. The reason is that mutations to the external store cannot be marked as non-blocking Transition updates, so they will trigger the nearestSuspense
fallback, replacing already-rendered content on screen with a loading spinner, which typically makes a poor UX.For example, the following are discouraged:
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사용법
Subscribing to an external store외부 스토어 구독하기
Most of your React components will only read data from their props, state, and context. However, sometimes a component needs to read some data from some store outside of React that changes over time. This includes: 대부분의 React 컴포넌트는 props, state, context에서만 데이터를 읽습니다. 하지만 때로는 컴포넌트가 시간이 지남에 따라 변경되는 React 외부의 저장소에서 데이터를 읽어야 하는 경우도 있습니다. 여기에는 다음이 포함됩니다:
-
Third-party state management libraries that hold state outside of React. React 외부에 state를 보관하는 서드파티 상태 관리 라이브러리.
-
Browser APIs that expose a mutable value and events to subscribe to its changes. 변이 가능한 값을 노출하는 브라우저 API와 그 변이 사항을 구독하는 이벤트.
Call useSyncExternalStore
at the top level of your component to read a value from an external data store.
외부 데이터 저장소에서 값을 읽으려면 컴포넌트의 최상위 레벨에서 useSyncExternalStore
를 호출하세요.
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
// ...
}
It returns the snapshot of the data in the store. You need to pass two functions as arguments: 스토어에 있는 데이터의 snapshot을 반환합니다. 두 개의 함수를 인수로 전달해야 합니다:
-
The
subscribe
function should subscribe to the store and return a function that unsubscribes.subscribe
함수는 스토어를 구독해야 하고, 구독 취소 함수를 반환해야 합니다. -
The
getSnapshot
function should read a snapshot of the data from the store.getSnapshot
함수는 스토어에서 데이터의 스냅샷을 읽어야 합니다.
React will use these functions to keep your component subscribed to the store and re-render it on changes. React는 이 함수를 사용해 컴포넌트가 스토어를 구독한 상태로 유지하고 변경 사항이 있을 때 다시 렌더링합니다.
For example, in the sandbox below, todosStore
is implemented as an external store that stores data outside of React. The TodosApp
component connects to that external store with the useSyncExternalStore
Hook.
예를 들어, 아래 샌드박스에서 todosStore
는 React 외부에 데이터를 저장하는 외부 store로 구현되어 있습니다. TodosApp
컴포넌트는 useSyncExternalStore
훅으로 해당 외부 스토어에 연결합니다.
import { useSyncExternalStore } from 'react'; import { todosStore } from './todoStore.js'; export default function TodosApp() { const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot); return ( <> <button onClick={() => todosStore.addTodo()}>Add todo</button> <hr /> <ul> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> </> ); }
Subscribing to a browser API브라우저 API 구독하기
Another reason to add useSyncExternalStore
is when you want to subscribe to some value exposed by the browser that changes over time. For example, suppose that you want your component to display whether the network connection is active. The browser exposes this information via a property called navigator.onLine
. This value can change without React’s knowledge, so you should read it with useSyncExternalStore
.
useSyncExternalStore
를 추가하는 또 다른 이유는 브라우저상의 시간이 지남에 따라 변경되는 일부 값을 구독하려는 경우입니다. 예를 들어, 컴포넌트에 네트워크 연결이 활성화되어 있는지 여부를 표시하고 싶다고 가정해 보겠습니다. 브라우저는 navigator.onLine
이라는 속성을 통해 이 정보를 노출합니다. 이 값은 시간이 지남에 따라 React가 알지 못하는 사이에 변경될 수 있으므로 useSyncExternalStore
로 값을 읽어야 합니다.
import { useSyncExternalStore } from 'react';
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ...
}
To implement the getSnapshot
function, read the current value from the browser API:
getSnapshot
함수를 구현하려면 브라우저 API에서 현재 값을 읽습니다:
function getSnapshot() {
return navigator.onLine;
}
Next, you need to implement the subscribe
function. For example, when navigator.onLine
changes, the browser fires the online
and offline
events on the window
object. You need to subscribe the callback
argument to the corresponding events, and then return a function that cleans up the subscriptions:
다음으로 subscribe
함수를 구현해야 합니다. 예를 들어, navigator.onLine
이 변경되면 브라우저는 window
객체에서 online
및 offline
이벤트를 실행합니다. callback
인수를 해당 이벤트에 구독한 다음 구독을 해제하는 함수를 반환해야 합니다:
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
Now React knows how to read the value from the external navigator.onLine
API and how to subscribe to its changes. Disconnect your device from the network and notice that the component re-renders in response:
이제 React는 외부 navigator.onLine
API에서 값을 읽는 방법과 그 변경 사항을 구독하는 방법을 알고 있습니다. 네트워크에서 디바이스의 연결을 끊어보면 컴포넌트가 다시 렌더링되는 것을 확인할 수 있습니다:
import { useSyncExternalStore } from 'react'; export default function ChatIndicator() { const isOnline = useSyncExternalStore(subscribe, getSnapshot); return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; } 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); }; }
Extracting the logic to a custom Hook사용자 정의 훅으로 로직 추출하기
Usually you won’t write useSyncExternalStore
directly in your components. Instead, you’ll typically call it from your own custom Hook. This lets you use the same external store from different components.
일반적으로 컴포넌트에서 직접 useSyncExternalStore
를 작성하지는 않습니다. 대신 일반적으로 사용자 정의 훅에서 호출합니다. 이렇게 하면 서로 다른 컴포넌트에서 동일한 외부 저장소를 사용할 수 있습니다.
For example, this custom useOnlineStatus
Hook tracks whether the network is online:
예를 들어, 이 사용자 정의 useOnlineStatus
훅은 네트워크가 온라인 상태인지 여부를 추적합니다:
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return isOnline;
}
function getSnapshot() {
// ...
}
function subscribe(callback) {
// ...
}
Now different components can call useOnlineStatus
without repeating the underlying implementation:
이제 다른 컴포넌트에서 기본 구현을 반복하지 않고도 useOnlineStatus
를 호출할 수 있습니다:
import { useOnlineStatus } from './useOnlineStatus.js'; function StatusBar() { const isOnline = useOnlineStatus(); return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>; } function SaveButton() { const isOnline = useOnlineStatus(); function handleSaveClick() { console.log('✅ Progress saved'); } return ( <button disabled={!isOnline} onClick={handleSaveClick}> {isOnline ? 'Save progress' : 'Reconnecting...'} </button> ); } export default function App() { return ( <> <SaveButton /> <StatusBar /> </> ); }
Adding support for server rendering서버 렌더링의 지원 추가하기
If your React app uses server rendering, your React components will also run outside the browser environment to generate the initial HTML. This creates a few challenges when connecting to an external store: React 앱이 서버 렌더링을 사용하는 경우, React 컴포넌트는 브라우저 환경 외부에서도 실행되어 초기 HTML을 생성합니다. 이로 인해 외부 스토어에 연결할 때 몇 가지 문제가 발생합니다:
-
If you’re connecting to a browser-only API, it won’t work because it does not exist on the server. 브라우저 전용 API에 연결하는 경우 서버에 해당 API가 존재하지 않으므로 작동하지 않습니다.
-
If you’re connecting to a third-party data store, you’ll need its data to match between the server and client. 타사 데이터 저장소에 연결하는 경우 서버와 클라이언트 간에 일치하는 데이터가 필요합니다.
To solve these issues, pass a getServerSnapshot
function as the third argument to useSyncExternalStore
:
이러한 문제를 해결하려면 getServerSnapshot
함수를 useSyncExternalStore
의 세 번째 인수로 전달하세요:
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
return isOnline;
}
function getSnapshot() {
return navigator.onLine;
}
function getServerSnapshot() {
return true; // Always show "Online" for server-generated HTML
}
function subscribe(callback) {
// ...
}
The getServerSnapshot
function is similar to getSnapshot
, but it runs only in two situations:
getServerSnapshot
함수는 getSnapshot
과 유사하지만 오직 두 가지 상황에서만 실행됩니다:
-
It runs on the server when generating the HTML. HTML을 생성할 때 서버에서 실행됩니다.
-
It runs on the client during hydration, i.e. when React takes the server HTML and makes it interactive. hydration중, 즉,React가 서버 HTML을 가져와서 인터랙티브하게 만들 때 클라이언트에서 실행됩니다.
This lets you provide the initial snapshot value which will be used before the app becomes interactive. If there is no meaningful initial value for the server rendering, omit this argument to force rendering on the client. 이를 통해 앱이 상호작용하기 전에 사용될 초기 스냅샷 값을 제공할 수 있습니다. 서버 렌더링에 의미 있는 초기값이 없다면 컴포넌트가 클라이언트에서만 렌더링되도록 강제 설정할 수 있습니다.
Troubleshooting문제 해결
I’m getting an error: “The result of getSnapshot
should be cached”오류가 발생했습니다: “getSnapshot
의 결과를 캐시해야 합니다.”
This error means your getSnapshot
function returns a new object every time it’s called, for example:
이 오류는 getSnapshot
함수가 호출될 때마다 새 객체를 반환한다는 의미입니다, 예컨대:
function getSnapshot() {
// 🔴 Do not return always different objects from getSnapshot
// 🔴 getSnapshot에서 항상 다른 객체를 반환하지 마세요
return {
todos: myStore.todos
};
}
React will re-render the component if getSnapshot
return value is different from the last time. This is why, if you always return a different value, you will enter an infinite loop and get this error.
React는 getSnapshot
반환값이 지난번과 다르면 컴포넌트를 다시 렌더링합니다. 때문에 항상 다른 값을 반환하면 무한 루프에 빠지게 되어 이 오류가 발생합니다.
Your getSnapshot
object should only return a different object if something has actually changed. If your store contains immutable data, you can return that data directly:
getSnapshot
은 실제로 변경된 사항이 있는 경우에만 다른 객체를 반환해야 합니다. 스토어에 불변 데이터가 포함된 경우 해당 데이터를 직접 반환할 수 있습니다:
function getSnapshot() {
// ✅ You can return immutable data
// ✅ 불변데이터는 반환할 수 있습니다
return myStore.todos;
}
If your store data is mutable, your getSnapshot
function should return an immutable snapshot of it. This means it does need to create new objects, but it shouldn’t do this for every single call. Instead, it should store the last calculated snapshot, and return the same snapshot as the last time if the data in the store has not changed. How you determine whether mutable data has changed depends on your mutable store.
스토어 데이터가 변이 가능한 경우 getSnapshot
함수는 해당 데이터의 불변 스냅샷을 반환해야 합니다. 즉,새 객체를 생성해야 하지만, 이 작업을 매 호출시마다 수행해서는 안 됩니다. 대신 마지막으로 계산된 스냅샷을 저장하고, 저장소의 데이터가 변경되지 않은 경우 지난번과 동일한 스냅샷을 반환해야 합니다. 변이 가능한 데이터가 변이되었는지를 확인하는 방법은 저장소가 구현된 방식에 따라 다릅니다.
My subscribe
function gets called after every re-render다시 렌더링할 때마다 subscribe
함수가 호출됩니다.
This subscribe
function is defined inside a component so it is different on every re-render:
이 subscribe
함수는 컴포넌트 내부에 정의되므로 다시 렌더링할 때마다 달라집니다:
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// 🚩 Always a different function, so React will resubscribe on every re-render
// 🚩 항상 다른 함수이므로 React는 매 렌더링시마다 다시 구독합니다
function subscribe() {
// ...
}
// ...
}
React will resubscribe to your store if you pass a different subscribe
function between re-renders. If this causes performance issues and you’d like to avoid resubscribing, move the subscribe
function outside:
재렌더링 사이에 다른 subscribe
함수를 전달하면 React가 스토어를 다시 구독합니다. 이로 인해 성능 문제가 발생하거나 스토어 재구독을 피하고 싶다면 subscribe
함수를 외부로 이동하세요:
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ...
}
// ✅ Always the same function, so React won't need to resubscribe
// ✅ 항상 동일한 함수이므로 React는 이를 재구독할 필요가 없습니다
function subscribe() {
// ...
}
Alternatively, wrap subscribe
into useCallback
to only resubscribe when some argument changes:
또는 일부 인수가 변경될 때만 다시 구독하도록 subscribe
을 useCallback
으로 감싸세요:
function ChatIndicator({ userId }) {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ✅ Same function as long as userId doesn't change
const subscribe = useCallback(() => {
// ...
}, [userId]);
// ...
}