0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jotaiの解説

0
Last updated at Posted at 2026-08-01

本記事の執筆はAIを使用しています。

はじめに

React向け状態管理ライブラリ「Jotai」の特徴、メリット・デメリット、基本的な使い方を紹介します。

「Jotai」とは?

Jotaiは、状態をatomという小さな単位で管理するReact向けの状態管理ライブラリです。

コンポーネントは必要なatomだけを購読し、そのatomが更新されたときに再レンダリングされます。
複数のatomを組み合わせて、別の状態を計算する派生atomも作成できます。
読み取りと更新を分離できるため、画面から状態操作の詳細を隠す設計にも向いています。

環境

  • React: 19.2.3
  • Jotai: 2.17.1
  • TypeScript: 5.x
  • pnpm: 11.9.0

Jotaiのメリット

小さく始められる

基本となるAPIは、atomを作る atom と、Reactコンポーネントから利用するHooksです。
useAtom はReactの useState と同じように、現在値と更新関数を返します。
そのため、既存のReactの知識を活かして導入できます。

必要な状態だけを購読できる

状態を複数のatomに分けると、コンポーネントは利用するatomだけを購読できます。
無関係なatomの更新では再レンダリングされないため、状態の利用範囲を小さく保てます。

状態から別の状態を宣言的に作れる

派生atomを使うと、既存のatomから計算した値を定義できます。
依存元のatomが更新されると、派生atomの値も再計算されます。
計算処理を各コンポーネントへ重複して書かずに済みます。

読み取りと更新を分離できる

useAtomValue は値の読み取り、 useSetAtom は値の更新だけを行います。
更新専用のatomを用意すると、複数のatomを変更する処理を一箇所へまとめられます。

Jotaiのデメリット

atomが増えると管理ルールが必要になる

細かく分割できる一方で、atomの数が増えると配置場所や命名に迷いやすくなります。
機能単位でファイルをまとめる、外部公開するatomを絞るなどのルールが必要です。

依存関係を追いづらくなることがある

派生atomを何段も組み合わせると、値の取得元や更新の影響範囲が見えにくくなります。
単純な計算は派生atomにまとめつつ、複雑な業務処理は用途がわかる関数やHookへ分けると読みやすくなります。

サーバー状態管理の機能は別途検討が必要になる

Jotaiは非同期のatomも扱えますが、APIレスポンスのキャッシュ、再取得、無効化などをすべて自動で提供するライブラリではありません。
サーバー状態が複雑なアプリでは、データ取得ライブラリとの責務分担を決める必要があります。

チーム内で設計がばらつきやすい

自由度が高いため、コンポーネントが多数のatomを直接操作する設計にもできます。
画面は用途別のHookを使い、atomの組み合わせや更新手順をHookまたは更新専用atomに隠すと責務を整理できます。

Jotaiの使い方

インストールする

次のコマンドでJotaiをインストールします。

$ pnpm add jotai

atomを作成する

atom に初期値を渡すと、書き込み可能なatomを作成できます。

src/features/counter/model/counterAtoms.ts
import { atom } from "jotai";

export const countAtom = atom(0);

atomの定義自体は値を保持せず、値はJotaiのstoreに保存されます。
atomは参照の同一性で識別されるため、基本的にはコンポーネントの外で定義します。
コンポーネント内で動的に作成する場合は、 useMemo などで参照を安定させる必要があります。

useAtomで読み書きする

useAtom にatomを渡すと、現在値と更新関数を取得できます。

src/features/counter/ui/Counter.tsx
import { useAtom } from "jotai";

import { countAtom } from "../model/counterAtoms";

export default function Counter() {
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <p>カウント: {count}</p>
      <button type="button" onClick={() => setCount((current) => current + 1)}>
        1増やす
      </button>
    </div>
  );
}

この時点では Provider を置かなくても、Jotaiのデフォルトのstoreが使われます。

読み取りと更新を分ける

値だけが必要なコンポーネントでは useAtomValue を使います。
更新だけが必要なコンポーネントでは useSetAtom を使います。

src/features/counter/ui/CounterParts.tsx
import { useAtomValue, useSetAtom } from "jotai";

import { countAtom } from "../model/counterAtoms";

export function CountLabel() {
  const count = useAtomValue(countAtom);

  return <p>カウント: {count}</p>;
}

export function IncrementButton() {
  const setCount = useSetAtom(countAtom);

  return (
    <button type="button" onClick={() => setCount((current) => current + 1)}>
      1増やす
    </button>
  );
}

IncrementButtoncountAtom の値を購読しないため、値が変わってもその変更だけを理由には再レンダリングされません。

派生atomを作成する

atom に読み取り関数を渡すと、ほかのatomから値を計算する読み取り専用atomを作成できます。

src/features/counter/model/counterAtoms.ts
import { atom } from "jotai";

export const countAtom = atom(0);

export const doubledCountAtom = atom((get) => get(countAtom) * 2);

get で読み取ったatomは依存関係として追跡されます。
countAtom が更新されると、 doubledCountAtom の値も再計算されます。

src/features/counter/ui/DoubledCount.tsx
import { useAtomValue } from "jotai";

import { doubledCountAtom } from "../model/counterAtoms";

export default function DoubledCount() {
  const doubledCount = useAtomValue(doubledCountAtom);

  return <p>2倍の値: {doubledCount}</p>;
}

更新処理をatomへまとめる

第1引数へ null、第2引数へ書き込み関数を渡すと、更新専用atomを作成できます。

src/features/counter/model/counterAtoms.ts
import { atom } from "jotai";

export const countAtom = atom(0);

export const incrementCountAtom = atom(null, (_get, set) => {
  set(countAtom, (current) => current + 1);
});

コンポーネントは更新手順を知らず、用途を表す関数を実行するだけになります。

src/features/counter/ui/IncrementButton.tsx
import { useSetAtom } from "jotai";

import { incrementCountAtom } from "../model/counterAtoms";

export default function IncrementButton() {
  const increment = useSetAtom(incrementCountAtom);

  return (
    <button type="button" onClick={increment}>
      1増やす
    </button>
  );
}

この方法は、複数のatomを一度に更新するときや、更新条件を画面から隠したいときに便利です。

Providerでstoreの範囲を分ける

Provider は配下のコンポーネントへ独立したstoreを提供します。

src/App.tsx
import { Provider } from "jotai";

import Counter from "./features/counter/ui/Counter";

export default function App() {
  return (
    <Provider>
      <Counter />
    </Provider>
  );
}

画面やサブツリーごとに状態を分離したい場合や、 Provider の再マウントで状態を初期化したい場合に利用します。

値をlocalStorageへ保存する

jotai/utilsatomWithStorage を使うと、atomの値を localStorage などへ同期できます。

src/features/theme/model/themeAtoms.ts
import { atomWithStorage } from "jotai/utils";

export const darkModeAtom = atomWithStorage("darkMode", false);

このatomは通常の書き込み可能なatomと同じように利用できます。
SSRを使う場合は、サーバーで利用できる初期値とブラウザのストレージに保存された値が異なり、初回表示で差異が生じる可能性があります。

設計するときのポイント

  • atomは機能単位で配置する
  • 表示だけのコンポーネントへatomの更新手順を持ち込まない
  • 値だけを使う場合は useAtomValue を使う
  • 更新だけを行う場合は useSetAtom を使う
  • 複雑な更新は更新専用atomまたは用途別のHookにまとめる
  • API由来の状態と画面内の状態の責務を分ける

Jotaiはatomを自由に組み合わせられるため、ディレクトリ構成と公開範囲を先に決めると規模が大きくなっても追いやすくなります。

おわりに

Jotaiは、Reactの状態を小さなatomに分割し、必要な場所から組み合わせて使える状態管理ライブラリです。
小さく導入できる一方で、atomの配置や依存関係を整理するルールが重要です。
まずは画面をまたいで共有したい状態を一つatomに置き換え、必要に応じて派生atomや更新専用atomへ広げると理解しやすいです :relaxed:

参考リンク

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?