概要
- Vanilla JS と React を比較しながら、同じUIを異なる実装思想で構築する方法を解説
- React における
useStateの基本的な使い方を整理 -
e.currentTarget.valueを使って入力値を取得する方法を確認 - コンポーネントの切り方・
propsの渡し方・importの方法を体系的に確認 - 「命令的UI更新(Vanilla)」と「宣言的UI更新(React)」の違いをコードで比較
同じ「EmailSender」アプリを
Vanilla JS と React の両方で実装することで設計思想の違いを理解する
実施条件
- React + Vite プロジェクトが構築済みであること
- React Hooks(
useState)の基本を理解していること - JavaScript のイベント処理の基礎理解があること
環境
| ツール | バージョン | 目的 |
|---|---|---|
| Node.js | 22.5.1 | 実行環境 |
| React | 19.1.0 | UI構築 |
| Vite | 5.x | 開発サーバ |
| JavaScript | ES2022 | イベント処理 |
アプリ仕様(EmailSender)
- メールアドレスを入力
- 「Send」ボタン押下で送信完了メッセージ表示
- 送信後は入力欄とボタンを無効化
- Enterキーでも送信できる(formのsubmitとして扱う)
Vanilla JS 実装(命令的UI)
ディレクトリ構成
/vanilla-app
├── index.html
└── index.js
index.html(<form> で input/button をまとめる)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>EmailSender (Vanilla)</title>
</head>
<body>
<div>
<!-- formにまとめることで Enter 送信(submit)を自然に扱える -->
<form id="emailForm">
<input
id="emailInput"
type="email"
placeholder="email@example.com"
required
/>
<button id="sendButton" type="submit">Send</button>
</form>
<p id="message"></p>
</div>
<script src="index.js"></script>
</body>
</html>
index.js
// ============================
// DOM取得
// ============================
const emailFormEl = document.getElementById("emailForm");
const emailInputEl = document.getElementById("emailInput");
const sendButtonEl = document.getElementById("sendButton");
const messageEl = document.getElementById("message");
// ============================
// 状態
// ============================
let state = {
email: "",
submitted: false,
};
// ============================
// 状態更新
// ============================
function setState(partial) {
state = { ...state, ...partial };
render();
}
// ============================
// 画面更新
// ============================
function render() {
// 入力欄の更新
emailInputEl.value = state.email;
emailInputEl.disabled = state.submitted;
// ボタンの更新
sendButtonEl.disabled = state.submitted || state.email === "";
// メッセージの更新
messageEl.textContent = state.submitted
? `送信しました: ${state.email}`
: "";
}
// ============================
// イベント登録
// ============================
// 入力変更(emailのstate更新)
emailInputEl.addEventListener("input", (e) => {
setState({ email: e.target.value });
});
// submit(ボタン押下でも Enter でもここに集約される)
emailFormEl.addEventListener("submit", (e) => {
// formのデフォルト挙動(ページリロード)を止める
e.preventDefault();
// required や type="email" のHTML標準バリデーションが有効な場合、
// 不正なら submit が発火しない(ブラウザがブロック)ことが多い
setState({ submitted: true });
});
// 初期描画
render();
Vanillaの特徴
- DOMを直接取得
- 自分で状態管理
- 自分で再描画関数を呼ぶ
- UI更新はすべて命令的
-
<form>を使うことで submitイベントに統一できる(Enter送信も自然)
React 実装(宣言的UI)
ディレクトリ構成
/react-app
├── index.html
├── main.jsx
├── App.jsx
└── components
├── EmailInput.jsx
├── SubmitButton.jsx
└── Message.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" />
<title>EmailSender (React)</title>
</head>
<body>
<div id="root"></div>
<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(<form onSubmit> に変更)
// 1. importセクション
import { useState } from "react";
import EmailInput from "./components/EmailInput.jsx";
import SubmitButton from "./components/SubmitButton.jsx";
import Message from "./components/Message.jsx";
// 2. 型定義セクション
// 今回は不要
// 3. 関数定義セクション
export default function App() {
// 3.1 内部状態管理セクション
const [email, setEmail] = useState("");
const [submitted, setSubmitted] = useState(false);
// 3.2 イベントハンドラーセクション
const handleSubmit = (e) => {
// formのデフォルト挙動(ページリロード)を止める
e.preventDefault();
setSubmitted(true);
};
// 3.4 返り値構築・ロジックセクション
return (
<div>
{/* formで包むことで Enter 送信も submit に統一できる */}
<form onSubmit={handleSubmit}>
<EmailInput
value={email}
disabled={submitted}
onChange={setEmail}
/>
{/* type="submit" にして submit として扱う */}
<SubmitButton
type="submit"
disabled={submitted || email === ""}
/>
</form>
<Message
submitted={submitted}
email={email}
/>
</div>
);
}
components/EmailInput.jsx
// 1. importセクション
import React from "react";
// 3. 関数定義セクション
const EmailInput = ({ value, disabled, onChange }) => {
// 3.2 イベントハンドラーセクション
const handleChange = (e) => {
// e.currentTarget.value を使用
onChange(e.currentTarget.value);
};
// 3.4 返り値構築・ロジックセクション
return (
<input
type="email"
placeholder="email@example.com"
value={value}
disabled={disabled}
required
onChange={handleChange}
/>
);
};
export default EmailInput;
components/SubmitButton.jsx(type を受け取れるように変更)
const SubmitButton = ({ disabled, type = "button" }) => {
return (
<button disabled={disabled} type={type}>
Send
</button>
);
};
export default SubmitButton;
components/Message.jsx
const Message = ({ submitted, email }) => {
return (
<p>
{submitted ? `送信しました: ${email}` : ""}
</p>
);
};
export default Message;
コンポーネントの切り方
Appは状態管理
子コンポーネントは表示責務のみ
| コンポーネント | 役割 |
|---|---|
| App | 状態管理・ロジック |
| EmailInput | 入力UI |
| SubmitButton | ボタンUI |
| Message | 表示UI |
propsの渡し方
親側
<EmailInput value={email} disabled={submitted} onChange={setEmail} />
子側
const EmailInput = ({ value, disabled, onChange }) => {}
import方法
import EmailInput from "./components/EmailInput.jsx";
-
export defaultの場合は好きな名前でimport可能
Vanilla vs React 比較
| 観点 | Vanilla JS | React |
|---|---|---|
| UI更新 | 手動DOM操作 | state変更で自動再描画 |
| 状態管理 | 自前 | useState |
| 入力取得 | e.target.value | e.currentTarget.value |
| 送信イベント | form submit | form onSubmit |
| コンポーネント | なし | 再利用可能 |