🐳

Recoil Atom Effects で Firebase Authentication の認証状態を管理【前編】

2022/03/14に公開約3,600字

はじめに

Recoil のAtom Effectsという機能を使えば Firebase Authentication の認証状態をシンプルに管理することができます。
この記事ではまずAtom Effectsについて解説してから、後編で具体的な実装を説明します。

  • この記事【前編】
    • Atom Effects とは?
    • Atom Effects の詳細
    • Atom Effects の使用例
    • Atom Effects 使用における注意点
  • 次回の記事【後編】
    • Firebase Authentication との使用例

Recoil とは?atom とは?

Recoil は React の状態管理ライブラリです。
atomは Recoil の中で状態を保持する単位です。
この記事では Recoil 自体の解説はしません。

https://recoiljs.org/

Atom Effects とは?

atomにとっての Atom Effects は コンポーネントによってのuseEffectみたいなものです。
Atom Effect を使って下記のようなことができます。

  • 副作用の管理
  • atom の初期化
  • atom とデータストアの同期

Atom Effects がuseEffectと違う点はライフサイクルがコンポーネントでは無く、atomと紐づいているということです。

useEffectを使ったフックのように違うコンポーネントがつかうたびに実行されたりしません。atom の初期化時に一度だけ実行されます。
そしてこの特徴が Firebase Authentication を使った認証状態管理と相性がいいです。

Atom Effects の詳細

以下のようにatomを定義する際にeffectプロパティに複数のAtom Effects function を設定できます。
Atom Effects function は atom が初めて使われたときに 1 度だけ実行されます。
(後述するようにクリーンアップされた場合は再初期化時に再度実行されます。)

const myState = atom({
  key: 'MyKey',
  default: null,
  effects: [
    () => {
      ...effect 1...
      return () => ...cleanup effect 1...;
    },
    () => { ...effect 2... },
  ],
});

また、Atom Effects function はいくつかの便利な引数をとることができます。特に以下が重要です。

  • setSelf: 自身の atom の値を更新するための関数。値かコールバック関数を引数にとる。
  • onSet: atom の値の変更があるたびに引数に入れたコールバック関数を実行してくれる。(ただし上記の setself で値を更新した場合は実行されない)

Atom Effects の使用例

ここでは公式の簡単な使用例を使って利用方法を説明します。

ステートをロギングする例

1 つ目の例は state が変わるたびに、その値をロギングする例です。onSetで変更があるたびにログを出力しています。

const currentUserIDState = atom({
  key: 'CurrentUserID',
  default: null,
  effects: [
    ({ onSet }) => {
      onSet((newID) => {
        console.debug('Current user ID:', newID);
      });
    },
  ],
});

リモートストレージとのデータ同期の例

2 つ目の例ではリモートストレージとデータを同期する例です。myRemoteStorageの実装はありませんが、良くある外部ストレージからデータをフェッチする API だと思ってください。

trigger'get' | 'set'で初期化時のアクションを示しています。ここではこの atom が初めて使われたアクションがgetだった場合だけmyRemoteStorage.get(userID)というリモートからのデータをこの atom の値に設定しています。

またmyRemoteStorage.onChangeではじまる部分では外部データの更新をサブスクライブして変更があるたびに atom に新しい値を設定しています。このような非同期な処理も可能です。

そして最後にreturnぼ部分で外部データの監視をキャンセルする関数を返しています。Atom Effects function はクリーンアップのための関数を返すことができます。このクリーンアップ関数が発火する条件は後述します。

const syncStorageEffect =
  (userID) =>
  ({ setSelf, trigger }) => {
    // Initialize atom value to the remote storage state
    if (trigger === 'get') {
      // Avoid expensive initialization
      setSelf(myRemoteStorage.get(userID)); // Call synchronously to initialize
    }

    // Subscribe to remote storage changes and update the atom value
    myRemoteStorage.onChange(userID, (userInfo) => {
      setSelf(userInfo); // Call asynchronously to change value
    });

    // Cleanup remote storage subscription
    return () => {
      myRemoteStorage.onChange(userID, null);
    };
  };

const userInfoState = atomFamily({
  key: 'UserInfo',
  default: null,
  effects: (userID) => [historyEffect(`${userID} user info`), syncStorageEffect(userID)],
});

注意点

非同期にデータを同期する方法はとても便利そうですが、1 つ注意すべき点があります。

それはクリーンアップ関数が発火するのは atom が所属する<RecoilRoot> が unmount される時だけだという点です。

そして 通常のアプリでは<RecoilRoot>は上流の方に 1 つだけなのでほぼ unmount される機会はほぼありません。

つまりアプリの使用中ずっとサブスクライブしておくようなデータであればこの方法は便利なのですが、アプリの一部でしか使わないデータをこの方法でサブスクライブすると使用後もアンサブスクライブされないままになってしまいます。

これに対するワークアラウンドもいくつかありますがそれは今回は割愛します。別記事で書くかもしれません。
またはいい方法を知っていたら教えてください!

Firebase Authentication と使う

認証状態は通常アプリ内で常に使う情報なので Atom Effects を使ってサブスクライブするのに向いています。具体的な方法は別記事で数日内に上げる予定です!

後編書きました。

https://zenn.dev/riemonyamada/articles/a55599711fa437

Discussion

ログインするとコメントできます