📂

Jotai のステートってどこに定義するべき?

2024/12/17に公開

普段 React でアプリ開発をしており、Jotai ライブラリを用いています。

https://jotai.org/

Jotai では atom を用いて、グローバルな状態管理を始めとした色々なことができます。
( Jotai そのものについての詳細は公式サイトや他記事を参考にしてください 🙏 )

const countAtom = atom(0);

さて早速ですが本題です。☝️この定義、どのディレクトリにあるどのファイルに定義すればよいのでしょうか?

私はなんとなく、 atom 定義専用のディレクトリを作りたいです……この気持ちは一体何なんでしょう。これを掘り下げて自分なりに納得した記録です。📝

用途から考える

(万人が必ず当てはまるわけでは無いと思いますが…)
我々の Jotai の使い方は、用途別に大きく3つに分けられます。

  1. 状態をグローバルに保持するため
  2. 外部からのデータ取得を容易にするため
  3. 外部から取得したデータをキャッシュするため

1. 状態をグローバルに保持するため

この用途で使われる atom は読み取り/更新するために、コード上のあらゆる場所で参照される値です。
意図しない場所で変更されててアプリ全体の挙動が壊れた‼️などが無いように、「グローバルな値である」ということが分かりやすくなっているべきです。

よって、定義する場所から「グローバルな値である」ことをアピールしておきたいです。

こんなかんじ
📂 src
 ┣ 📁 app
 ┣ 📁 hooks
 ┣ 📁 navigators
 ┣ 📁 screens
 ┗ 📂 globalStateAtoms
    ┣ 📄 countAtom.ts
    ┗ 📄 settingAtom.ts

2. 外部からのデータ取得を容易にするため

これはデータベースからの読み取り専用値などで、主に atomWithObservable を用いてリアルタイムに変更が反映されてほしいような値です。
この atom は基本的にはグローバルに参照されるものではないです。

const historiesAtom = atomWithObservable(() => DBにアクセスするObservableを返す関数);

このような atom を扱う場合は、基本的にカスタムフックを定義します。🌟
(※規模やルールや好みにも依ると思います…)

export const useHistories = () => {
    const histories = useAtomValue(historiesAtom);
    return histories;
};

コード上のあちこちで呼び出されるのは useHistories であり、 historiesAtomuseHistories の中でしか呼ばれません。
となると、この atom はカスタムフックと同じファイルにローカルとして定義すれば充分そうです。

3. 外部から取得したデータをキャッシュするため

これはある程度の時間は更新しなくてもよいプラットフォームからの値などで、 atomFamily を用いて同じ引数であれば外部通信せずにキャッシュを返したい値です。

const maximumTemperatureAtomFamily = atomFamily(
    (param: Param) => atom(async () => fetchMaximumTemperature(param)),
    (a: Param, b: Param) => a.year === b.year && a.month === b.month && a.day === b.day
);

// 1時間経ったらキャッシュを無効化
maximumTemperature.setShouldRemove(
    (createdAt: number) => createdAt < subHours(Date.now(), 1)
);

この場合も2.と同様、基本的にはカスタムフックを定義します。🌟

export const useMaximumTemperature(param: Param) => {
    const maximumTemperature = useAtomValue(maximumTemperatureAtomFamily(param));
    return maximumTemperature;
};

2.と同じく、この atom もカスタムフックと同じファイルにローカルとして定義すれば充分そうです。

つまりはこんなかんじ
📂 src
 ┣ 📁 app
 ┣ 📂 hooks
    ┣ 📄 useHistories.ts  👈️ この中で atom 定義&利用
    ┗ 📄 useMaximumTemperature.ts  👈️ この中で atom 定義&利用
 ┣ 📁 navigators
 ┣ 📁 screens
 ┗ 📂 globalStateAtoms
    ┣ 📄 countAtom.ts
    ┗ 📄 userAtom.ts

まとめ

atom を定義するディレクトリを分けたい理由は、ファイルのある階層から「変更には他の場所にも影響あるかも ⚠️ 」の注意書き看板を立てたいという気持ちが私は大きかったです。

  • グローバル変数の使い方をする atom は専用ディレクトリで定義。
  • そうでなければ必要になる場所でローカル定義。

後者なんだけど、1つの atom を複数 hooks で使いたいんだよネ… 🤔
とかの場合は検討の余地アリです。

💬 よいディレクトリ構成があったらぜひ教えてください 👐

余談

🌟 なぜカスタムフックを経由するのか

データ取得方法を隠蔽するため。

💭 このデータを保持してるのはatomなのか、atomFamilyなのか、atomWithObservableなのか…
カスタムフックは☝️これを隠蔽するインタフェースの役割を担っています。
(あとカスタムフックを介したほうが単体テストも実装しやすいし‼️)

Discussion