useSyncExternalStore 完全入門【React】外部ストアへのサブスクライブ
ReactのuseSyncExternalStoreとは何か、Reactの外部で管理されるデータ(ブラウザAPI・外部ライブラリ)をコンポーネントで安全に購読する方法を解説します。
Reactでは通常、useState や useReducer で状態を管理します。しかし、Reduxのような外部ライブラリやブラウザのAPIなど、Reactの外部で管理されるデータを使いたい場面があります。
useSyncExternalStore は、こうした外部データソースに安全にサブスクライブするためのフックです。
この記事でわかること:
useSyncExternalStoreが必要な理由- 3つのパラメータの役割
- ブラウザAPIへの接続例
- カスタムフックとしてのラップパターン
useSyncExternalStore とは?
useSyncExternalStore は、React の外部にあるデータストアを購読(サブスクライブ)するフックです。
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);| パラメータ | 説明 |
|---|---|
subscribe | ストアへのサブスクライブを開始し、クリーンアップ関数を返す |
getSnapshot | ストアの現在のデータを返す関数 |
getServerSnapshot(省略可能) | SSR 用の初期スナップショットを返す関数 |
戻り値はストアの現在のスナップショット(最新の値)です。
なぜ useEffect だけではダメなのか?
外部ストアのサブスクライブを useEffect だけで実装すると、並行レンダリングにおいて**データの不整合(ティアリング)**が起きる可能性があります。useSyncExternalStore はこの問題を安全に解決します。
実用例1:ネット接続状態の監視
import { useSyncExternalStore } from "react";
// ① スナップショット取得関数
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);
};
}
function NetworkStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return (
<p style={{ color: isOnline ? "green" : "red" }}>
{isOnline ? "オンライン" : "オフライン"}
</p>
);
}実用例2:ブラウザのスクロール位置
import { useSyncExternalStore } from "react";
function getScrollSnapshot() {
return window.scrollY;
}
function subscribeScroll(callback) {
window.addEventListener("scroll", callback);
return () => window.removeEventListener("scroll", callback);
}
function ScrollIndicator() {
const scrollY = useSyncExternalStore(subscribeScroll, getScrollSnapshot);
return (
<div style={{ position: "fixed", top: 0, right: 0, padding: "8px" }}>
スクロール位置: {scrollY}px
</div>
);
}カスタムフックとしてラップする
ロジックを再利用可能にするため、カスタムフックとしてまとめるのが一般的なパターンです。
import { useSyncExternalStore } from "react";
// カスタムフック:オンライン状態を提供する
function useOnlineStatus() {
return useSyncExternalStore(
subscribeOnlineStatus,
getOnlineSnapshot,
() => true // SSR時はtrueを返す
);
}
function subscribeOnlineStatus(callback) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}
function getOnlineSnapshot() {
return navigator.onLine;
}
// どこからでも使い回せる
function Header() {
const isOnline = useOnlineStatus();
return <header>{isOnline ? "● オンライン" : "○ オフライン"}</header>;
}重要な注意点
getSnapshot は安定した参照を返す
// ❌ NG:毎回新しいオブジェクトを返すと無限ループになる
function getSnapshot() {
return { data: store.data }; // 毎回新しいオブジェクト
}
// ✅ OK:変化がなければ同じ参照を返す
let cachedSnapshot = null;
function getSnapshot() {
if (store.data !== lastData) {
cachedSnapshot = { data: store.data };
lastData = store.data;
}
return cachedSnapshot;
}subscribe は安定した関数を使う
// ❌ NG:コンポーネント内で定義すると毎回再サブスクライブされる
function MyComponent() {
// subscribe がレンダリングのたびに新しい関数になる
const data = useSyncExternalStore(
(callback) => { /* ... */ }, // ← 毎回新しい関数
getSnapshot
);
}
// ✅ OK:コンポーネントの外に定義する
function subscribe(callback) { /* ... */ }
function MyComponent() {
const data = useSyncExternalStore(subscribe, getSnapshot);
}どんな場面で使うか
| 場面 | 使うフック |
|---|---|
| React 内の状態管理 | useState / useReducer |
| Reactの外部のストア | useSyncExternalStore |
| データフェッチ・タイマー | useEffect |
Redux などの状態管理ライブラリは内部で useSyncExternalStore を使うことが推奨されています。ライブラリを使う側では直接触れる機会は少ないですが、ブラウザAPIをReactコンポーネントで扱うときに便利です。
まとめ
useSyncExternalStoreはReact外部のデータストアを安全に購読するフックsubscribe・getSnapshot・getServerSnapshotの3つのパラメータを持つ- ブラウザAPI(オンライン状態・スクロール位置など)との連携に使える
- カスタムフックとしてラップして再利用するのが一般的パターン
getSnapshotは同じデータなら同じ参照を返すようにする