お好きな金額でサポートしていただけると嬉しいです!
(50円〜10,000円[税込]まで)
作成日時:2025年10月15日 23:45
更新日時:2025年10月15日 23:45
React
Next.js
(追記:この記事は2024年頃から過去に投稿した内容を不定期で更新し再投稿したものです)
ReactにおけるGlobal State管理の方法について、useSWRを含めた方法をまとめておこうと思ったのでここに残します。
まずはpropsで渡す方法。
// page.tsx
"use client";
import State from "./State";
import { useState } from "react";
export interface Props {
value: string;
}
export default function Home() {
const [sampleState] = useState("この文字はpropsで渡しました");
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
<State value={sampleState} />
</main>
);
}
// State.tsx
import React from "react";
import { Props } from "./page";
const State = (props: Props) => {
return (
<div>
<h2>Propsで管理</h2>
{props.value}
</div>
);
};
export default State;
<結果>
きちんと渡されますが、階層が深くなるにつれ、いわゆるバケツリレーが発生します。
小規模な開発ならまだしも、中規模以上なら他の方法を検討した方がよいでしょう。
React標準の機能であるuseContextを使うパターン。
以下の手順で利用できます。
※追記:React19でuse
を使ってuseContext()を代替できます(後述)
1.createContext()を使ってコンテキストを作成
// MyContext.tsx
import { createContext } from "react";
export const MyContext = createContext<string | null>("この文字は初期値です");
2.コンテキストプロバイダーに値を渡す
※追記:React19で.Provider
を省略できるようになりました。
// page.tsx
"use client";
import State from "./State";
import { useState } from "react";
import { MyContext } from "./MyContext";
export default function Home() {
const [sampleText] = useState("この文字はuseContextで渡しました");
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
{/* コンテキストプロバイダーに値を渡す */}
<MyContext value={sampleText}>
<State />
</MyContext>
</main>
);
}
3.useContext()を使ってデータを取得
// State.tsx
import React, { useContext } from "react";
import { MyContext } from "./MyContext";
const State = () => {
const sampleText: string | null = useContext(MyContext);
return (
<div>
<h2>useContextで管理</h2>
<p>{sampleText}</p>
</div>
);
};
export default State;
<結果>
「この文字はuseContextで渡しました」と表示され、きちんと渡せていることが確認できます。
注意点として、コンテキストの箱に入っている値が複数の場合、そのうちどれか一つでも更新されると、
そのコンテキストが紐づく全コンポーネントが再レンダリングされます。
必要最低限の再レンダリングにするために、複数のコンテキストを作る、もしくはmemo化をして管理するようにしましょう。
※タブなどによるコンポーネント表示のだし分けを親コンポーネントが行っている場合、現在レンダリングされていない(タブ表示されていない)子コンポーネントは、仮にすべての子コンポーネントがcontextを購読していたとしても、コンポーネントツリーに表示されていないコンポーネントがマウントされていない状態となるため、再レンダリングされることはありません。
つまり、複数のコンテキストを作ったり、memo化を行わなくても副次効果的に無駄な再レンダリングを防ぐことができますが、それはあくまで副次効果にすぎず、安全な実装ではありません。
バグの原因となるため、useContextを使う部分はそれのみで再レンダリングに対するコントロールをきちんとしておくことが大切です。
React 19で導入された新しいuse
フックを使った状態管理方法です。 従来のuseContext
の進化版で、より柔軟な使い方が可能になりました。
まずcreateContext()を使ってコンテキストを作成します。
// MyContext.tsx
import { createContext } from "react";
export const MyContext = createContext<string>("この文字は初期値です");
その後、使いたいコンポーネントでコンテキストをインポートして使うだけです。
// page.tsx
"use client";
import State from "./State";
import { useState } from "react";
import { MyContext } from "./MyContext";
export default function Home() {
const [sampleText] = useState("この文字はuseで渡しました");
return (
<main>
<MyContext value={sampleText}>
<State />
</MyContext>
</main>
);
}
また、use()を使ってデータを取得するやり方も。
// State.tsx
import React from "react";
import { use } from "react";
import { MyContext } from "./MyContext";
const State = () => {
const sampleText = use(MyContext);
return (
<div>
<h2>useで管理</h2>
<p>{sampleText}</p>
</div>
);
};
export default State;
その他、useContextではできない条件分岐・ループ内での利用がそれぞれ可能です。
これにより、例えば条件付きでコンテキストにアクセスすることで、不要な再レンダリングを防ぎ、パフォーマンスを柔軟に最適化できたりします。
React19以降を使うなら、useを使ってしまった方がいいと思います。
import { use } from 'react';
import FeatureContext from './FeatureContext';
function FeatureComponent({ featureName }) {
let featureEnabled = false;
// 条件分岐内でuseを使用できる
if (featureName) {
featureEnabled = use(FeatureContext);
}
return featureEnabled
? <div>機能が有効です。</div>
: <div>機能が無効です。</div>;
}
import { use } from 'react';
import SettingsContext from './SettingsContext';
function SettingsDisplay({ settingKeys }) {
const settingElements = settingKeys.map(key => {
// ループ内でuseを使用できる
const settings = use(SettingsContext);
return <div key={key}>{key}: {settings[key]}</div>;
});
return <div>{settingElements}</div>;
}
useContextのようにPropsで値を受け渡すことなくState管理する方法の一つです。
2015年から存在しているMeta社提唱のFluxアーキテクチャに則って設計されている状態管理のライブラリであり、
後述するRecoilの登場まではデファクトスタンダードに近いポジションを取っていたものです。
特に大規模なプロジェクトに適しているといわれています。
単一方向にしかデータが流れない、というところが特徴。
■Redux公式サイト
https://redux.js.org/
※参考図:公式サイトから引用
1.Store・・・アプリケーション全体の状態を保持する場所で、単一のオブジェクトです。これにより、状態を一元管理できます。
2.Action・・・アプリケーション内で何かが起こったことを示すオブジェクトです。アクションは状態の変更のトリガーとして機能し、必要な情報を含みます。
3.Reducer・・・アクションを受け取り、新しい状態を生成する関数です。リデューサーは純粋な関数であるため、同じ入力に対しては常に同じ出力を生成します。
4.Dispatch・・・ アクションを送信して、リデューサーによる状態の更新をトリガーします。これにより、アプリケーションの状態が変更されます。
Reduxを利用するには、reduxとreact-reduxをインストールします。
@reduxjs/toolkitには、Reduxのコアパッケージと、Reduxアプリの構築に不可欠な主要パッケージが含まれています。
react-reduxはreactのコンポ-ネントからreduxにアクセスするためのライブラリです。
npm install @reduxjs/toolkit react-redux
※単体でインストールするなら以下。
npm install redux react-redux
storeを作成。reducerは後ほど作る。
また、今回TypeScriptを使用しているため、RootState型を定義してエクスポートしておきます。
// MyStore.ts
import { configureStore } from "@reduxjs/toolkit";
import textReducer from "./reducers/textSlice";
const store = configureStore({
reducer: {
text: textReducer,
},
});
export default store;
// RootState型を定義してエクスポート
export type RootState = ReturnType<typeof store.getState>;
次に、状態をSliceに定義し、Reducerを作成します。
// textSlice.tsx
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
message: "この文字は初期値です",
};
const textSlice = createSlice({
name: "sampleText",
initialState,
reducers: {
setMessage: (state, action) => {
state.message = action.payload; // メッセージを設定するReducerを作成
},
},
});
export const { setMessage } = textSlice.actions; // 新しいReducerをエクスポート
export default textSlice.reducer;
※補足:
・State: 状態を保持するオブジェクトで、アプリの使用中に値は変化します。
・Action:更新の内容やタイプを指示するオブジェクトです。
・Reducer:Stateをアップデートする関数です。引数としてstateとactionを持ちます。
・Slice:Redux Toolkitに含まれる状態のデフォルト値やアクションを定義するオブジェクトです。
呼び出していきます。Providerタグでコンポーネントを囲むことでRedux Storeが使えるようになります。
// page.tsx
import State from "./State";
import { Provider } from "react-redux";
import store from "./MyStore";
export default function Home() {
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
{/* Providerタグでコンポーネントを囲むことでRedux Storeが使える */}
<Provider store={store}>
<State />
</Provider>
</main>
);
}
使いたいコンポーネント側で呼び出します。
// State.tsx
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { setMessage } from "./reducers/textSlice";
import { RootState } from "./MyStore"; //型をインポート
export default function State() {
const message = useSelector((state: RootState) => state.text.message);
const dispatch = useDispatch();
const handleClick = () => {
dispatch(setMessage("この文字はReducerを使って渡しました"));
};
return (
<div>
<h2>Stateコンポーネント</h2>
<p>メッセージ: {message}</p>
<button onClick={handleClick}>ボタンをクリック</button>
</div>
);
}
これでボタンをクリックすると「メッセージ:この文字はReducerを使って渡しました」と表示されるはずなのですが、
依存関係のエラーが出て使えなかったので、動作確認はできておりません。
万が一誤り等あれば、CONTACTページからご指摘いただけますと幸いです。
ちなみに、ここでは解説しませんが、今はredux-react の useSelector Hooksを利用することができるようです。
useSelectorに置き換えることでコードがスッキリします。
Meta社が2020年に公開した新しい状態管理のライブラリでしたが、2025年1月、メンテナーがいなくなったことで、お役目を終えました。
一応、背景情報として知っておいても損はないと思いますので、使い方含めて残しておくことにします。
Reduxのような学習コストがかかるものではなく、useStateと同じような感覚で使用することが可能です。
Reduxと同様にFluxというアーキテクチャに則って設計されており、Atom や Selector と呼ばれるものを使用して管理を行います。
まずはインストールします。
npm i recoil
続いて、atomを設定します。
//MyAtom.tsx
import { atom } from "recoil";
export const MyText = atom({
key: "MyText",
default: "この文字はRecoilを使って渡しました",
});
早速使えるようにしていきます。
// page.tsx
"use client";
import { RecoilRoot } from "recoil";
import State from "./State";
export default function Home() {
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
{/* RecoilRootタグでコンポーネントを囲むことで使えます */}
<RecoilRoot>
<State />
</RecoilRoot>
</main>
);
}
きちんと表示できました。
※2024/7/1追記:
layout.tsxは基本サーバーコンポーネントであるため、ページ丸ごとで状態管理したい場合に、layout.tsxでRecoilRootを使用したいケースがよく発生します。
そういった時は、以下のようにWrapperとなるコンポーネントを作成して、それをlayout.tsxでインポートして使用することで、クライアントコンポーネントとサーバーコンポーネントをきっちり分けることができます。
ご参考まで。
"use client";
import { RecoilRoot } from "recoil";
const RecoilRootWrapper = ({ children }: { children: React.ReactNode }) => {
return <RecoilRoot>{children}</RecoilRoot>;
};
export default RecoilRootWrapper;
useSWRは、Next.jsを開発しているVercelが開発した、Reactアプリケーションでデータの取得とキャッシュを管理するためのカスタムフックライブラリです。
useSWRは、データを非同期的にフェッチ、キャッシュし、必要に応じて再利用することができます。
これにより、APIリクエストやクエリの結果を簡単に管理し、アプリケーションのパフォーマンスを向上させることができます。
まずuseSWRフックの使い方から。
以下のようにuseSWRを使用することで、データのフェッチとキャッシュ、エラーハンドリング、ローディング表示などを簡単に管理できます。
import useSWR from 'swr';
function fetchData(url) {
const { data, error } = useSWR(url, fetch); // URLをキーとしてデータを取得
if (error) {
return <div>エラーが発生しました</div>;
}
if (!data) {
return <div>データを読み込んでいます...</div>;
}
return <div>データ: {data}</div>;
}
状態管理のためにuseSWRを使用したい場合、第2引数の fetcherを渡す箇所に対して、nullを渡します。
これにより、useSWRを使ったGlobal Stateの管理が行えるようになります。
const { data:sampleText, error:setSampleText } = useSWR(url, sampleText, null, { initialData: 'この文字は初期値です' });
useSWRはmutate関数を使用することでデータを更新できるので、これによってuseStateのような使い方ができるというわけです。
また、useSWRの第3引数には様々なoptionを指定できるので、initialDataの部分でデータの初期値を設定しておくことができます。
Zustandは状態管理のためのライブラリで、useContextのContextの値が変更されると、すべてのコンポーネントが再レンダリングされてしまう問題を回避できる一つの方法です。
ストアを準備して、使いたいコンポーネントで使うだけ。公式にも書かれている通りReduxに似ていますが、それよりもさらに簡単に使うことができます。
■Zustand公式サイト
https://zustand.docs.pmnd.rs/getting-started/introduction
まずはストアを作成します。
// /store/useStore.tsx
import { create } from "zustand";
interface StoreState {
content: string;
}
const useStore = create<StoreState>(() => ({
content: "Zustandで本文を取得しました"
}));
export default useStore;
そして、使いたいコンポーネントで使います。
"use client";
import useStore from "./store/useStore";
export default function Home() {
const { content } = useStore();
return (
<div>
<p>{content}</p>
</div>
);
}
Jotaiは、Recoilと似たコンセプトを持つ軽量な状態管理ライブラリです。
2025年1月にRecoilのリポジトリがアーカイブされたため、Recoilの代替としてJotaiへ移行する事例が増えています。
Atomという小さな単位で状態を管理し、useStateのようにシンプルで直感的に扱うことができます。
■Jotai公式ドキュメント
https://jotai.org/docs
// atoms.tsx
import { atom } from 'jotai';
export const textAtom = atom<string>("この文字はJotaiを使って渡しました");
JotaiはProviderが不要です(オプションで使用可)。
// page.tsx
"use client";
import State from "./State";
export default function Home() {
return (
<main>
<h1>ReactにおけるGlobal state管理方法</h1>
{/* Providerなしで直接使える */}
<State />
</main>
);
}
// State.tsx
import React from "react";
import { useAtom } from "jotai";
import { textAtom } from "./atoms";
const State = () => {
const [text, setText] = useAtom(textAtom);
return (
<div>
<h2>Jotaiで管理</h2>
<p>{text}</p>
<button onClick={() => setText("テキストが更新されました")}>
更新
</button>
</div>
);
};
export default State;
値を読み取るだけの場合はuseAtomValue
を使用します。
import { useAtomValue } from "jotai";
import { textAtom } from "./atoms";
const DisplayOnly = () => {
const text = useAtomValue(textAtom);
return <p>{text}</p>;
};
値を更新するだけの場合はuseSetAtom
を使用します。
import { useSetAtom } from "jotai";
import { textAtom } from "./atoms";
const UpdateOnly = () => {
const setText = useSetAtom(textAtom);
return (
<button onClick={() => setText("更新")}>
更新
</button>
);
};
他のatomから計算された値を持つatomを作成できます。
// atoms.tsx
import { atom } from 'jotai';
export const countAtom = atom(0);
// 派生atom: countの2倍の値
export const doubleCountAtom = atom(
(get) => get(countAtom) * 2
);
// Component.tsx
import { useAtom, useAtomValue } from "jotai";
import { countAtom, doubleCountAtom } from "./atoms";
const Counter = () => {
const [count, setCount] = useAtom(countAtom);
const doubleCount = useAtomValue(doubleCountAtom);
return (
<div>
<p>カウント: {count}</p>
<p>2倍: {doubleCount}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
};
Jotaiは非同期処理も簡単に扱えます。
// atoms.tsx
import { atom } from 'jotai';
export const userAtom = atom(async () => {
const response = await fetch('/api/user');
return response.json();
});
// Component.tsx
import { Suspense } from "react";
import { useAtomValue } from "jotai";
import { userAtom } from "./atoms";
const UserDisplay = () => {
const user = useAtomValue(userAtom);
return <div>ユーザー名: {user.name}</div>;
};
// Suspenseで囲む必要がある
const App = () => {
return (
<Suspense fallback={<div>読み込み中...</div>}>
<UserDisplay />
</Suspense>
);
};
他にもReduxの代替え手段としてuseReducerを使う方法がありますし、 GraphQL APIをクライアント側で効率よく操作するためのライブラリApollo Clientを利用したState管理なんかもあったり色々存在しています。
更新の都度、だんだん量が多くなってきたので、今回紹介している各方法をまとめておきます。選定時の参考にしてみてください。
どのような開発環境や背景で、どういった規模で、何を選定をするのか状況に応じて使い分けをしていきましょう。
方法 | メリット | デメリット | 適用場面 |
---|---|---|---|
Props | シンプル、理解しやすい | バケツリレー発生 | 小規模、階層が浅い |
useContext | 標準機能、Provider必要 | 再レンダリング問題 | 中規模、テーマ管理など |
use (React 19) | 柔軟、条件分岐可能 | React 19以降のみ | useContextの置き換え |
ライブラリ | パッケージサイズ | 学習コスト | 適用場面 |
---|---|---|---|
Jotai | 約2KB | 低 | 中〜大規模、軽量重視 |
Zustand | 約3KB | 低 | 中規模、シンプル重視 |
Redux | 約10KB | 高 | 大規模、複雑な状態管理 |
useSWR | - | 低 | データフェッチ中心 |
引き続き、定期的に自分でも振り返りながら更新していけたらと思います。
それではまた。
HTNCode