概要
- Vanilla JS と React を比較しながら、同じUIを異なる実装思想で構築する方法を解説
- React における
useState・useEffectの基本的な使い方を整理 -
fetchを使って API(https://picsum.photos/300/300)から画像を取得する方法を紹介 - コンポーネントの切り方・
propsの渡し方・importの方法を体系的に確認 - 「命令的UI更新(Vanilla)」と「宣言的UI更新(React)」の違いをコードで比較
同じ「Picture Provider」アプリを
Vanilla JS と React の両方で実装することで設計思想の違いを理解する
実施条件
- React + Vite プロジェクトが構築済みであること
- React Hooks(
useState,useEffect)の基本を理解していること - JavaScript の非同期処理(
fetch,async/await)の基礎理解があること
環境
| ツール | バージョン | 目的 |
|---|---|---|
| Node.js | 22.5.1 | 実行環境 |
| React | 19.1.0 | UI構築 |
| Vite | 5.x | 開発サーバ |
| JavaScript | ES2022 | 非同期処理 |
アプリ仕様(Picture Provider)
- ボタンを押すとランダム画像を取得
- API:
https://picsum.photos/300/300 - 読み込み中は「Loading...」表示
- 初回マウント時にも自動取得
Vanilla JS 実装(命令的UI)
ディレクトリ構成
/vanilla-app
├── index.html
└── index.js
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>Picture Provider (Vanilla)</title>
</head>
<body>
<button id="refreshButton">Next Picture</button>
<img id="picture" width="300" height="300" />
<p id="status"></p>
<script src="index.js"></script>
</body>
</html>
index.js
// DOM取得
const button = document.getElementById("refreshButton");
const picture = document.getElementById("picture");
const status = document.getElementById("status");
// 状態
let state = {
imageUrl: "",
loading: false
};
// 状態更新
function setState(partial) {
state = { ...state, ...partial };
render();
}
// 画面更新
function render() {
button.disabled = state.loading;
status.textContent = state.loading ? "Loading..." : "";
picture.src = state.imageUrl;
}
// API呼び出し
async function loadImage() {
setState({ loading: true });
const res = await fetch("https://picsum.photos/300/300", {
cache: "no-store"
});
setState({
imageUrl: res.url,
loading: false
});
}
// イベント登録
button.addEventListener("click", loadImage);
// 初期実行
loadImage();
render();
Vanillaの特徴
- DOMを直接取得
- 自分で状態管理
- 自分で再描画関数を呼ぶ
- UI更新はすべて命令的
React 実装(宣言的UI)
ディレクトリ構成
/react-app
├── index.html
├── main.jsx
├── App.jsx
└── components
├── RefreshButton.jsx
├── Picture.jsx
└── Status.jsx
React Hooks の基本構造
- importセクション
- 型定義セクション
- 関数定義セクション
3.1 内部状態管理セクション
3.2 イベントハンドラーセクション
3.3 副作用(useEffect)処理セクション
3.4 返り値構築・ロジックセクション
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Picture Provider (React)</title>
</head>
<body>
<!-- Reactが描画するルート -->
<div id="root"></div>
<!-- Vite: エントリーポイント -->
<script type="module" src="/main.jsx"></script>
</body>
</html>
main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
App.jsx
// 1. importセクション
import { useState, useEffect } from "react";
import RefreshButton from "./components/RefreshButton.jsx";
import Picture from "./components/Picture.jsx";
import Status from "./components/Status.jsx";
// 2. 型定義セクション
// 今回は不要
// 3. 関数定義セクション
export default function App() {
// 3.1 内部状態管理セクション
const [imageUrl, setImageUrl] = useState("");
const [loading, setLoading] = useState(false);
// 3.2 イベントハンドラーセクション
const loadImage = async () => {
setLoading(true);
const res = await fetch("https://picsum.photos/300/300", {
cache: "no-store"
});
setImageUrl(res.url);
setLoading(false);
};
// 3.3 副作用(useEffect)処理セクション
useEffect(() => {
loadImage(); // 初回マウント時に実行
}, []);
// 3.4 返り値構築・ロジックセクション
return (
<div>
<RefreshButton loading={loading} onClick={loadImage} />
<Picture imageUrl={imageUrl} />
<Status loading={loading} />
</div>
);
}
components/RefreshButton.jsx
// 1. importセクション
import React from "react";
// 3. 関数定義セクション
const RefreshButton = ({ loading, onClick }) => {
// 3.4 返り値構築・ロジックセクション
return (
<button disabled={loading} onClick={onClick}>
Next Picture
</button>
);
};
export default RefreshButton;
components/Picture.jsx
const Picture = ({ imageUrl }) => {
return (
<img
src={imageUrl}
width={300}
height={300}
alt="random"
/>
);
};
export default Picture;
components/Status.jsx
const Status = ({ loading }) => {
return (
<p>
{loading ? "Loading..." : ""}
</p>
);
};
export default Status;
コンポーネントの切り方
Appは状態管理
子コンポーネントは表示責務のみ
| コンポーネント | 役割 |
|---|---|
| App | 状態管理・ロジック |
| RefreshButton | ボタンUI |
| Picture | 画像表示 |
| Status | 状態表示 |
propsの渡し方
<RefreshButton loading={loading} onClick={loadImage} />
子側で受け取る
const RefreshButton = ({ loading, onClick }) => {}
import方法
import RefreshButton from "./components/RefreshButton.jsx";
-
export defaultの場合は好きな名前でimport可能
Vanilla vs React 比較
| 観点 | Vanilla JS | React |
|---|---|---|
| UI更新 | 手動でDOM更新 | state更新で自動再描画 |
| 状態管理 | 自前 | useState |
| 初期実行 | 直接関数呼び出し | useEffect |
| コンポーネント | なし | 再利用可能 |