Reactで中〜大規模アプリを構築する際、「状態管理の整理」「フォルダ構成の一貫性」「保守性」が重要になります。
本記事では、Context API × MVC風構成を活用して、以下を実現するアーキテクチャの実装手順をご紹介します。
- グローバルな状態管理
- データ取得(API)の責務分離
- フォルダ構成の明確化(可読性・再利用性UP)
✅ 想定されるフォルダ構成
以下のような構成をベースに実装します。
src/
├── api/ # API呼び出し(Controller的役割)
│ └── countryApi.js
├── components/ # 再利用可能なUIコンポーネント
│ └── Header/
│ └── CountryDropdown.jsx
├── context/ # グローバル状態(Context API)
│ ├── CountryContext.jsx
│ └── CountryProvider.jsx
├── hooks/ # カスタムフック
│ └── useCountries.js
├── pages/ # ルーティングページ(View)
│ └── Home.jsx
├── services/ # ビジネスロジック(Model)
│ └── countryService.js
├── App.jsx # アプリのエントリポイント
├── index.js # ReactDOMのマウント
└── styles/ # 共通スタイル
🌐 1. Country API を分離(api/countryApi.js
)
// src/api/countryApi.js
import axios from "axios";
export const fetchCountries = async () => {
const res = await axios.get("https://countriesnow.space/api/v0.1/countries/");
return res.data.data.map((country) => country.country); // 国名だけ抽出
};
🧠 2. グローバル状態用のContextを作成(context/CountryContext.jsx
)
// src/context/CountryContext.jsx
import { createContext } from "react";
export const CountryContext = createContext();
🧩 3. プロバイダーコンポーネント(context/CountryProvider.jsx
)
// src/context/CountryProvider.jsx
import { useEffect, useState } from "react";
import { CountryContext } from "./CountryContext";
import { fetchCountries } from "../api/countryApi";
export const CountryProvider = ({ children }) => {
const [countryList, setCountryList] = useState([]);
useEffect(() => {
const load = async () => {
const countries = await fetchCountries();
setCountryList(countries);
};
load();
}, []);
return (
<CountryContext.Provider value={{ countryList }}>
{children}
</CountryContext.Provider>
);
};
🧱 4. コンポーネントでContextを使用(components/Header/CountryDropdown.jsx
)
import React, { useContext, useState } from "react";
import Dialog from "@mui/material/Dialog";
import Button from "@mui/material/Button";
import { IoCloseOutline } from "react-icons/io5";
import { CountryContext } from "../../context/CountryContext";
const CountryDropdown = () => {
const [isOpen, setIsOpen] = useState(false);
const { countryList } = useContext(CountryContext);
return (
<>
<Button onClick={() => setIsOpen(true)}>現在の国を選択</Button>
<Dialog open={isOpen} onClose={() => setIsOpen(false)}>
<div className="dialog-content">
<Button
className="close-button"
variant="text"
onClick={() => setIsOpen(false)}
>
<IoCloseOutline />
</Button>
<ul>
{countryList.map((country, index) => (
<li key={index}>
<Button onClick={() => setIsOpen(false)}>{country}</Button>
</li>
))}
</ul>
</div>
</Dialog>
</>
);
};
export default CountryDropdown;
🚀 5. AppコンポーネントでProviderをラップ(App.jsx
)
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { CountryProvider } from "./context/CountryProvider";
import Home from "./pages/Home";
import Header from "./components/Header";
function App() {
return (
<CountryProvider>
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
</CountryProvider>
);
}
export default App;
🧠 補足:なぜ分離が重要?
層 | 役割 |
---|---|
API層 (/api/ ) |
外部通信だけを担う(axiosなど) |
Service層 (/services/ ) |
ビジネスロジックの管理 |
Context (/context/ ) |
グローバルステート管理 |
Component | UIの表示と最小限のロジック |
Hooks | ロジックの再利用・分離 |
📌 まとめ
Context API を用いた状態管理において、責務を分離したフォルダ構成をとることで、以下が実現できます。
- 保守性の高いコード
- コンポーネントの再利用性向上
- チーム開発でも迷いづらい明瞭な責務分担
✍️ 最後に
これからReactアプリを中長期で運用していく予定がある方には、このような構成を初期から取り入れることを強くおすすめします。
お気に召しましたら、ぜひいいね・フォローお願いします 🙌
質問などありましたらお気軽にコメントください!