21
5

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

useSyncExternalStore について少しまとめてみた

Last updated at Posted at 2023-07-06

useSyncExternalStore とは

React18 で新たに追加された React フックで、外部ストアをサブスクライブできるようにする React Hook

どんな時に使えるか考察

外部ストアをサブスクライブできるようにする React Hook

上記だとあまりピンと来なかったのでもう少し調べてみると、ドキュメントに以下のようなことが書かれていました。

React コンポーネントの多くは props、state、context からデータを参照します。
ただし、React 外部のデータソースから値を参照する必要がある場合も存在します。
このような場合、 useSyncExternalStore を使うことによって、 React 外部のデータソースのストアを監視し、ストアの値を参照できます。

例 )
React の外部で状態を保持するライブラリ
変更可能な値とその変更をサブスクライブするブラウザのイベント API

正直、まだピンと来なかったので、もう少しドキュメントを読んでみて以下の記述を見つけました。

  1. useStateuseReducer を使用してReact で状態管理ができるなら、そちらを使用することをお勧めします。useSyncExternalStore API は、既存の非 React コードと統合する必要がある場合に主に役立ちます。
  2. 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);
  };
}

subscribegetSnapshot を 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 の方がだいぶ良さそうに思います!

21
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
5