Recoil Atom Effects で Firebase Authentication の認証状態を管理【前編】
はじめに
Recoil のAtom Effects
という機能を使えば Firebase Authentication の認証状態をシンプルに管理することができます。
この記事ではまずAtom Effects
について解説してから、後編で具体的な実装を説明します。
- この記事【前編】
- Atom Effects とは?
- Atom Effects の詳細
- Atom Effects の使用例
- Atom Effects 使用における注意点
-
次回の記事【後編】
- Firebase Authentication との使用例
Recoil とは?atom とは?
Recoil は React の状態管理ライブラリです。
atom
は Recoil の中で状態を保持する単位です。
この記事では Recoil 自体の解説はしません。
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 を使ってサブスクライブするのに向いています。具体的な方法は別記事で数日内に上げる予定です!
後編書きました。
Discussion