useSyncExternalStore とは
React18 で新たに追加された React フックで、外部ストアをサブスクライブできるようにする React Hook
どんな時に使えるか考察
外部ストアをサブスクライブできるようにする React Hook
上記だとあまりピンと来なかったのでもう少し調べてみると、ドキュメントに以下のようなことが書かれていました。
React コンポーネントの多くは props、state、context からデータを参照します。
ただし、React 外部のデータソースから値を参照する必要がある場合も存在します。
このような場合、 useSyncExternalStore を使うことによって、 React 外部のデータソースのストアを監視し、ストアの値を参照できます。例 )
React の外部で状態を保持するライブラリ
変更可能な値とその変更をサブスクライブするブラウザのイベント API
正直、まだピンと来なかったので、もう少しドキュメントを読んでみて以下の記述を見つけました。
-
useState
やuseReducer
を使用してReact で状態管理ができるなら、そちらを使用することをお勧めします。useSyncExternalStore
API は、既存の非 React コードと統合する必要がある場合に主に役立ちます。 -
useSyncExternalStore
を追加するもう 1 つの理由は、ブラウザーによって公開されて、状態変化する値をサブスクライブする場合です。
利用場面
色々調べたことを少し自分なりに解釈する下記のようになりました。
なんとなくイメージがしやすくなったかと思います。
-
非Reactコードと統合して状態管理をする必要がある場合
-
useState
/useReducer
/ ライブラリ 等を用いても React で状態管理ができない場合
-
- React を通さずにブラウザ側で起こった変化を管理する場合
使い方
今回は React でも検知が難しいブラウザの状態管理イベントを読み取る場合 で、ドキュメントにも記載がある、ネットワーク接続がアクティブかどうかをコンポーネントに表示したい場合をやっていきます。
ブラウザは、navigator.onLine というプロパティを介してネットワーク接続の状態を公開します。
そしてこの値は、React の知らないうちに変更される可能性があるため、useSyncExternalStore を使用して読み取る必要があります。
まずはネットワークの状況を、ページに表示するための枠組みを作ります。
パラメータや戻り値に関しての詳細は、こちらを参照ください。
import { useSyncExternalStore } from 'react';
function useOnlineStatus() {
// subscribe: ストアが変更されるたびに呼び出されるコールバックを登録する
// getSnapshot: 現在のストアの値を返す
return useSyncExternalStore(subscribe, getSnapshot);
}
export default function App() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
getSnapshot
関数を実装するには、ブラウザ API から現在の値を読み取ります。
function getSnapshot() {
return navigator.onLine;
}
subscribe
関数を実装するには、コールバック引数を対応するイベントに登録し、返り値には登録を解除する関数を返す必要があります。
navigator.onLine
が変更されると、ブラウザはウィンドウオブジェクトでオンラインとオフラインのイベントを発生させます。
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
subscribe
、getSnapshot
を useSyncExternalStore の引数に渡すことで、 navigator.onLine
の値を読み取り、変更をサブスクライブできます。結果として True か False を返します。
import { useSyncExternalStore } from 'react';
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
function getSnapshot() {
return navigator.onLine;
}
function useOnlineStatus() {
return useSyncExternalStore(subscribe, getSnapshot);
}
export default function App() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
番外編
ちなみに、useEffect
を使って実装するとこのようになります。
ただ今回説明したように、React 18以降は useSyncExternalStore
を使っていくのが良さそうです。
import { useState } from 'react';
function useOnlineStatus() {
// 理想的ではありません
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function updateState() {
setIsOnline(navigator.onLine);
}
updateState();
window.addEventListener('online', updateState);
window.addEventListener('offline', updateState);
return () => {
window.removeEventListener('online', updateState);
window.removeEventListener('offline', updateState);
};
}, []);
return isOnline;
}
export default function App() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
(useEffect
で実装した場合に、useSyncExternalStore
と比べてどのようなデメリットがあるかまで細かくは調べきれていないのと、少し時間がかかりそうなので、また追記したいと思います。。。)
ただ可読性については、useSyncExternalStore
の方がだいぶ良さそうに思います!