Aizu Advent Calendar 2024の6日が埋まってなかったので書いた。
ReactのuseSyncExternalStoreは名前に反して便利に使えるフックなので紹介したい。 ちなみに11月の学内LTと同じネタなので、知っている人は読まなくても良いと思う。
今回のuseSyncExternalStoreでブラウザAPIをラップするという話にも繋がるのだが、Reactから見たReact外の状態とブラウザAPIは同じという話をしたい。
これは学内LTで使ったスライドの一部なのだが、再掲させていただく。
Reactを書いているとき、状態はuseStateやuseReducerなどで扱うことが多い。これらはReactのコンポーネントと紐づき管理されており、Reactが値の更新を把握し、再レンダーを行なうことができる。しかしReactの管理外にある変数などを利用すると、値の更新などが把握できない。
これはブラウザAPIも同様である。ブラウザAPIはReactが管理していないので、値の更新が把握できない。
このようにReact外の状態とブラウザAPIには共通点がある。そして、一般的(主観)にはどちらもuseEffectを使い対応することが多いように思う。しかし、useSyncExternalStoreを利用するとどちらも簡単にラップすることができる。
公式のreact.devには、外部ストアへのサブスクライブを可能にするReactフックと書いてある。そして実は、ブラウザAPIへのサブスクライブという章がある。あまり読まれてない気がするのだが気のせいだろうか?
ともあれ、先ずはuseSyncExternalStoreの型定義は次のようになっている。
export function useSyncExternalStore<Snapshot>(
subscribe: (onStoreChange: () => void) => () => void,
getSnapshot: () => Snapshot,
getServerSnapShot?: () => Snapshot,
): Snapshot;
引数のsubscribeは、値のサブスクライブをする関数だ。値の変更があった再にonStoreChangeを呼び出すことで、ReactはgetSnapshotを実行し、戻り値をuseSyncExternalStoreの戻り値として返す。
subscribeが変更されるとReactは再サブスクライブをするということと、getSnapshotが変更されていないと再レンダーを実行しないという点が少し分かりづらい。subscribe関数はコンポーネント外で定義するか、useCallbackを使用する必要がある。
react.devで紹介されているnavigator.onLineをラップする例は次である。
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return isOnline;
}
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);
};
}
もちろんuseEffectでも実現可能ではあるが、useSyncExternalStoreを使用する例では直感的に記述できているように思う。
以上useSyncExternalStoreの紹介だった。あまり使う機会は多くないと思うが、おもしろいフックなので試してみてほしい。