この記事の最終更新日: 2025年5月27日
はじめに
フロントエンド開発では、コンポーネント間で状態をどのように共有するかが常に課題になります。Redux や Context API などの選択肢がありますが、Recoil は React の思想に寄り添いながらも、より直感的にグローバルステートを扱えるライブラリとして注目されています。本記事では、Recoil の基本概念から実装方法、他ライブラリとの比較、ベストプラクティスまでをわかりやすく解説します。

Recoil とは?
- Facebook 製:2020 年に公開された比較的新しい状態管理ライブラリ。
- 「共有可能なローカルステート」 を目指し、React コンポーネントでの状態管理をスムーズに。
- 学習コストが低い:React のフック API とよく似た書き味で導入しやすい。
💡 ポイント:Recoil は Context API より宣言的で、Redux よりボイラープレート(記述の手間)が少ないのが特徴です。
核心概念(用語の意味を丁寧に解説)
Recoil には 3 つの中心的な用語があります。
概念 | 役割 | キーワード |
---|---|---|
Atom(アトム) | アプリ全体で共有できる状態の最小単位。複数のコンポーネントで同じ値を扱える | “状態の源” |
Selector(セレクタ) | Atom や他の Selector を元に、派生した値(計算結果など)を作る関数 | “派生 & メモ化” |
Snapshot(スナップショット) | アプリの状態をある瞬間で記録・再生できる仕組み | “タイムトラベル” |
Atom
Atom は「状態の定義そのもの」です。React の useState に似ていますが、複数のコンポーネントで共有できます。
import { atom } from "recoil";
export const userState = atom<string | null>({
key: "userState", // 一意なキー(Recoil 内で重複しない必要あり)
default: null, // 初期値
});
Selector
Selector は Atom を元に派生した状態を計算します。たとえば「ログイン済みかどうか」などは、ユーザー情報(Atom)から判断できます。
import { selector } from "recoil";
import { userState } from "./atoms";
export const isLoggedInState = selector<boolean>({
key: "isLoggedInState",
get: ({ get }) => Boolean(get(userState)),
});
セットアップ手順(初心者にもわかるように)
1. Recoil のインストール
プロジェクトに Recoil を追加します。
npm install recoil
# または
yarn add recoil
2. アプリ全体を RecoilRoot で囲む
Recoil の機能を有効にするには、アプリの最上位を <RecoilRoot>
で囲みます。
import { RecoilRoot } from "recoil";
import App from "./App";
export default function Root() {
return (
<RecoilRoot>
<App />
</RecoilRoot>
);
}
3. 状態の読み書き
useRecoilState や useRecoilValue などのフックを使って、状態の操作ができます。
import { useRecoilState, useRecoilValue } from "recoil";
import { userState, isLoggedInState } from "./store";
function LoginButton() {
const [user, setUser] = useRecoilState(userState); // 状態を読み書き
const isLoggedIn = useRecoilValue(isLoggedInState); // 状態を読み取り専用
return isLoggedIn ? (
<button onClick={() => setUser(null)}>Logout</button>
) : (
<button onClick={() => setUser("Alice")}>Login</button>
);
}
非同期処理と Recoil
Recoil の Selector は async/await
に対応しており、API などからデータを取得して状態として扱えます。
以下はユーザー ID に応じてユーザー情報を取得する例です:
const userProfileQuery = selector({
key: "userProfileQuery",
get: async ({ get }) => {
const userId = get(userIdState);
if (!userId) return null;
const res = await fetch(`/api/users/${userId}`);
return res.json();
},
});
⏳ 自動キャッシュ:同じ ID で再度呼び出された場合、API へ再リクエストせずキャッシュされたデータを返します。
Recoil DevTools でデバッグ
開発中に状態の流れを可視化するために、Chrome 拡張などの DevTools を活用できます。
また、Snapshot
を使えば状態をログ出力したり、履歴として保存できます。
import { useRecoilSnapshot } from "recoil";
import { useEffect } from "react";
function DebugObserver() {
const snapshot = useRecoilSnapshot();
useEffect(() => {
console.debug("Snapshot", snapshot);
}, [snapshot]);
return null;
}
ベストプラクティス(実務でのコツ)
- Atom の粒度は小さく:状態が変わるたびに再レンダリングが発生するため、1 つの Atom に複数の状態を詰め込みすぎないようにしましょう。
- Selector を活用してロジック分離:計算処理をコンポーネント内に書かず、Selector に移して再利用しやすくします。
- 型安全に設計:TypeScript を使うことで、誤った状態の扱いを防ぎます。
- Async Selector の管理:API 呼び出しには
selectorFamily
を使い、引数付きでキャッシュを細かく管理しましょう。
Recoil と他ライブラリ比較(どれを選ぶべき?)
特徴 | Recoil | Redux Toolkit | Zustand | Jotai |
---|---|---|---|---|
学習コスト | 低(React に近い) | 中(設計の理解が必要) | 低(シンプルな構文) | 低(最小構成) |
ボイラープレート | 少ない | 少ない(Toolkit 使用時) | 少ない | 非常に少ない |
DevTools | 公式あり | 公式あり | 一部あり | 一部あり |
型安全サポート | ◎ | ◎ | ◯ | ◯ |
非同期サポート | 標準対応(Selector) | ミドルウェア追加必要 | 標準 | 標準 |
🔍 選定ポイント:フォームや API 呼び出しが多いアプリでは Recoil の相性が良いですが、2025 年以降はメンテナンス状況も含めて再検討が必要です。

大阪のエンジニアが書いているブログ。
コメント