3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ta1m1kamが一人で書くAdvent Calendar 2022

Day 25

ReactHooksについてまとめる(Other Hooks useSyncExternalStore )

Last updated at Posted at 2022-12-24

概要

ReactHooksのOther Hooksについてのメモ記事です。基本的にReactの公式ドキュメントの咀嚼記事となっています。

今回はuseSyncExternalStore編です。

useSyncExternalStore

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

外部ストアをサブスクライブする

ほとんどのReactコンポーネントは、props、state、contextからしかデータを読みません。しかし、時にはコンポーネントがReactの外部にあるストアから、データを読み込む必要があります。

  • Reactの外部に状態を保持するサードパーティの状態管理ライブラリ。
  • Reactの外部に状態を保持するサードパーティの状態管理ライブラリ。ミュータブルな値とその変更を購読するためのイベントを公開するブラウザAPI。

外部データストアから値を読み込むには、コンポーネントの最上位で useSyncExternalStore を呼び出します。

ストア内のデータのスナップショットを返します。引数として2つの関数を渡す必要があります。

1つ目は、subscribe関数はstoreに登録し、登録を解除する関数を返します。
2つ目は、getSnapshot関数は、ストアからデータのスナップショットを読み込む必要があります。
Reactはこれらの関数を使用して、コンポーネントをストアに登録したままにし、変更時に再レンダリングします。

以下のように、トップレベルの変数にを管理するtodoStoreがあるとします。ここには、subscribe関数とgetSnapshot関数を登録します。

todoStore.js
let nextId = 0;
let todos = [{ id: nextId++, text: 'Todo #1' }];
let listeners = [];

export const todosStore = {
  addTodo() {
    todos = [...todos, { id: nextId++, text: 'Todo #' + nextId }]
    emitChange();
  },
  subscribe(listener) {
    listeners = [...listeners, listener];
    return () => {
      listeners = listeners.filter(l => l !== listener);
    };
  },
  getSnapshot() {
    return todos;
  }
};

function emitChange() {
  for (let listener of listeners) {
    listener();
  }
}

TodosApp コンポーネントは、useSyncExternalStore フックでその外部ストアに接続します。

App.js
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>
    </>
  );
}

ブラウザAPIをサブスクライブする

ブラウザによって、変化する情報をサブスクライブする場合にも使用できます。
たとえば、ネットワーク接続が有効であるかどうかをコンポーネントに表示させたい場合があるとします。その際には navigator.onLineというプロパティでブラウザから情報を取得できます。このような値はReactの知らない所で変更される可能性があるので useSyncExternalStoreで読み取ります。

import { useSyncExternalStore } from 'react';

function ChatIndicator() {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot);
  // ...
}

現在のネットワーク接続を取得するために、getSnapshotの戻り値としてnavigator.onLineを設定します。

function getSnapshot() {
  return navigator.onLine;
}

次に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);
  };
}

これで、Reactは、navigator.onLine APIから値を読み取る方法と、変更をサブスクライブできるようになりました。

App.js
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);
  };
}

実際に手元の端末のネットワークを切断・接続してみるとコンポーネントが更新され表示が変化することがわかります。

参考

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?