React入門 - コンポーネント、JSX、Props、Stateの基本
目次
はじめに
React を学び始めたときに、最初に戸惑うのが「なぜこんな複雑なのか」という感覚です。jQuery で育ったなら、DOM を直接操作する命令的アプローチが自然に思えるでしょう。しかし、React は全く異なる思想です。
初学者にとって、Props と State の違いは特にわかりにくい概念です。「どちらを使うべき?」「なぜ Props は変更できないの?」こうした疑問は、誰もが通る道です。
本記事では、React の最も基本的な概念である コンポーネント、JSX、Props、State を、初心者の視点に立って解説します。これらの4つの概念を深く理解することが、React 開発の土台となり、その後の Hook やデータ管理ライブラリも自然に理解できるようになります。
実際のコード例と、著者が初学者時代に戸惑った「ハマりポイント」を含めて、学習の効率化を図りました。
Reactとは
ReactはFacebookが開発したJavaScriptライブラリで、ユーザーインターフェース(UI)を効率的に構築するためのツールです。最大の特徴は、コンポーネント指向の開発と、宣言的UIです。
宣言的UIとは
従来のJavaScript(jQuery など)では、状態が変わった時に「どのようにDOM を操作するか」を命令的に書きます。
// 命令的な書き方(jQuery)
const count = 0;
document.getElementById("count").textContent = count;
button.addEventListener("click", () => {
count++;
document.getElementById("count").textContent = count; // 手動で DOM を更新
});一方、Reactでは「現在の状態に対して、UIがどのように見えるべきか」を宣言します。状態が変わると、Reactが自動的にUIを更新します。
// 宣言的な書き方(React)
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}この違いは、Reactを学ぶ上で最も重要なマインドシフトです。
JSX とは
JSXは、JavaScriptの中にHTMLのような構文を埋め込める拡張言語です。正式名称は「JavaScript XML」です。
JSX の基本
JSXで書いた以下のコード:
const element = <h1>Hello React!</h1>;は、実際には以下のJavaScriptに変換されます:
const element = React.createElement("h1", null, "Hello React!");つまり、JSXは単なる糖衣構文(シンタックスシュガー)です。ReactのcreateElement関数の呼び出しを、より読みやすく書くためのものです。
JSX の利点
- 可読性が高い - HTML に近い記述で直感的に理解できる
- バリデーション - TypeScriptやESLintで構文チェックが可能
- プログラマブル - JavaScriptの全機能が使える
function WelcomeBanner({ userName, isAdmin }) {
return (
<div>
<h1>Welcome {userName}</h1>
{isAdmin && <p>You have admin privileges</p>}
<p>Today is {new Date().toLocaleDateString()}</p>
</div>
);
}JSX の約束事
JSXを書く際には、いくつかのルールがあります。
1. 単一のルート要素を返す
// ❌ エラー: 複数の要素
function Bad() {
return (
<h1>Title</h1>
<p>Description</p>
);
}
// ✅ OK: ラッパー要素で囲む
function Good() {
return (
<div>
<h1>Title</h1>
<p>Description</p>
</div>
);
}
// ✅ OK: Fragment を使う
function Good2() {
return (
<>
<h1>Title</h1>
<p>Description</p>
</>
);
}2. className を使う(class ではなく)
// ❌ class は JavaScript の予約語なので使えない
<div class="container">...</div>
// ✅ className を使う
<div className="container">...</div>3. 属性名はキャメルケース
// ❌ ハイフンは使わない
<input on-change={handleChange} />
// ✅ キャメルケースで書く
<input onChange={handleChange} />4. イベント名もキャメルケース
// ❌ 間違い
<button onclick={handleClick}>Click</button>
// ✅ 正しい
<button onClick={handleClick}>Click</button>コンポーネント
Reactの最小単位はコンポーネントです。UIを小さな再利用可能なパーツに分割して、組み合わせることでアプリケーションを構築します。
関数コンポーネント
React v16.8 以降、関数コンポーネントが標準になりました。単純にJavaScript関数を定義するだけです。
function Welcome() {
return <h1>Hello React!</h1>;
}
// 使用方法
function App() {
return <Welcome />;
}重要なルール: コンポーネント名は大文字で始める
// ✅ OK - 大文字で始まる
function MyComponent() {
return <div>...</div>;
}
// ❌ NG - 小文字で始まるのはHTML要素と見なされる
function myComponent() {
return <div>...</div>;
}コンポーネントの作成パターン
// パターン1: シンプルな関数
function SimpleButton() {
return <button>Click me</button>;
}
// パターン2: JSX を return する関数
function ProductCard() {
return (
<div className="card">
<h2>Product Name</h2>
<p>Price: $99</p>
</div>
);
}
// パターン3: 複数行の処理後に JSX を return
function UserGreeting() {
const currentHour = new Date().getHours();
const greeting = currentHour < 12 ? "Good morning" : "Good afternoon";
return <h1>{greeting}</h1>;
}Props(プロップス)
Props は、親コンポーネントが子コンポーネントに渡すデータです。コンポーネント間の通信の基本となります。
Props の基本
// 子コンポーネント
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 親コンポーネント
function App() {
return <Greeting name="Alice" />;
}分割代入を使った書き方
Props を分割代入することで、コードがより簡潔になります。
// Before: props.name, props.age, ...
function UserCard(props) {
return (
<div>
<h2>{props.name}</h2>
<p>Age: {props.age}</p>
<p>Email: {props.email}</p>
</div>
);
}
// After: 分割代入を使う
function UserCard({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
// 使用例
function App() {
return <UserCard name="Bob" age={30} email="bob@example.com" />;
}Props の型定義(TypeScript で安全に)
TypeScript を使うと、Props の型を明示的に定義でき、実装時のミスを防ぐことができます。
// ステップ1: Props の型を interface で定義
interface UserCardProps {
name: string; // 必須の文字列
age: number; // 必須の数値
email?: string; // オプショナル(?で省略可能)
isActive?: boolean; // オプショナル
}
// ステップ2: コンポーネントで型を指定
function UserCard({ name, age, email = 'unknown', isActive = true }: UserCardProps) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
<p>{isActive ? 'Active' : 'Inactive'}</p>
</div>
);
}
// 使用例
<UserCard name="Alice" age={30} /> // ✅ OK(email, isActive は省略)
<UserCard name="Bob" age={25} email="bob@..." /> // ✅ OK
// <UserCard name="Charlie" /> // ❌ エラー:age が必須型定義のポイント:
?をプロパティ名の後に付けるとオプショナルになる- デフォルト値は
=で指定できる - 間違った型を渡すと IDE で即座に警告される
Props として関数を渡す
コンポーネント間の通信で、親が子に関数を渡すことも一般的です。
interface ButtonProps {
label: string;
onClick: () => void;
}
function CustomButton({ label, onClick }: ButtonProps) {
return <button onClick={onClick}>{label}</button>;
}
function App() {
const handleClick = () => {
alert("Button clicked!");
};
return <CustomButton label="Click me" onClick={handleClick} />;
}Props は読み取り専用
重要な原則: Props は読み取り専用で、変更してはいけません。 Props に基づいて値を計算する場合は、新しい変数を作成します。
// ✅ 正しい: Props から新しい値を計算
function UserProfile({ name, age }: { name: string; age: number }) {
const displayName = name.toUpperCase(); // Props を読むだけ
const isAdult = age >= 18; // Props から計算
return (
<div>
<h1>{displayName}</h1>
<p>{isAdult ? "Adult" : "Minor"}</p>
</div>
);
}State(ステート)
State は、コンポーネント内で管理される、時間とともに変わるデータです。Props と異なり、State は コンポーネント自身が変更できます。
useState Hook の基本
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}useState は以下のように機能します:
- 初期値:
useState(0)で初期値を0に設定 - 戻り値:
[現在の値, 更新関数]のペアを返す - 状態の更新:
setCount(新しい値)で状態を更新 - 再レンダリング: 状態が更新されると、コンポーネントが再レンダリングされる
State の実践例
import { useState } from "react";
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
// ログイン処理
try {
const response = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ email, password }),
});
if (response.ok) {
alert("Login successful!");
}
} catch (error) {
alert("Login failed!");
} finally {
setIsLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit" disabled={isLoading}>
{isLoading ? "Logging in..." : "Login"}
</button>
</form>
);
}Props と State の違い
| 特性 | Props | State |
|---|---|---|
| 誰が所有するか | 親コンポーネント | そのコンポーネント自身 |
| 変更可能か | No(不変) | Yes(変更可能) |
| 用途 | 親から子へのデータ受け渡し | コンポーネント内のデータ管理 |
| 初期値の設定 | 親が設定 | useState で設定 |
| 変更時の動作 | 変更しないこと | setState で変更し、再レンダリング |
State 更新の落とし穴
1. 前の状態に基づいて更新する場合は関数を使う
// ❌ 間違い: 複数回クリックすると期待と異なる結果になることがある
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1); // これは効果がない
};
return <button onClick={handleClick}>Count: {count}</button>;
}
// ✅ 正しい: 関数形式を使う
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1); // これは期待通りに動く
};
return <button onClick={handleClick}>Count: {count}</button>;
}2. State はオブジェクトを含むことができるが、直接変更しない
// ❌ 間違い: オブジェクトを直接変更
function User() {
const [user, setUser] = useState({ name: "Alice", age: 25 });
const handleAgeIncrease = () => {
user.age = 26; // ❌ 直接変更
setUser(user); // React が変更を検出できない
};
}
// ✅ 正しい: 新しいオブジェクトを作成
function User() {
const [user, setUser] = useState({ name: "Alice", age: 25 });
const handleAgeIncrease = () => {
setUser({ ...user, age: user.age + 1 }); // スプレッド演算子で新しいオブジェクト
};
}Props と State の流れ(重要な概念)
React の最も重要なパターンは、**データが上から下に流れる(単一方向フロー)**ということです。
┌─────────────────────────────────────────┐
│ App(State を管理) │
│ const [message, setMessage] │
└──────────────┬──────────────────────────┘
│ Props として message を渡す
↓
┌──────────────────────────────────────┐
│ Parent(Props を受け取る) │
│ function Parent({ message }) │
└──────────────┬───────────────────────┘
│ さらに Props として子に渡す
↓
┌──────────────────────────────────────┐
│ Child(最終的に表示) │
│ <h1>{message}</h1> │
└──────────────────────────────────────┘
このように、データは常に親から子へと流れます。逆向きは不可です。
function App() {
const [message, setMessage] = useState("Hello");
return (
<div>
{/* Props として子に渡す */}
<Parent message={message} />
<button onClick={() => setMessage("Hi")}>Change Message</button>
</div>
);
}
function Parent({ message }: { message: string }) {
return (
<div>
{/* さらに孫に渡す */}
<Child message={message} />
</div>
);
}
function Child({ message }: { message: string }) {
return <h1>{message}</h1>;
}このパターンでは:
Appがmessageという State を持つAppがmessageを Props としてParentに渡すParentがmessageを Props としてChildに渡すChildは Props としてmessageを受け取り表示する
データは常に上から下へ流れます。子から親への直接的な変更はできません。
実践例:Todo アプリ
ここまでの概念を組み合わせた、簡単な Todo アプリの例です。このアプリは以下を実装しています:
- 複数の State 管理 - todos 配列と入力フィールドの状態
- 子コンポーネントへの Props 渡し - todo データとコールバック関数
- イベントハンドリング - クリック、入力、キープレスイベント
- リスト操作 - map、filter などを使った配列操作
import { useState } from "react";
interface Todo {
id: number;
text: string;
completed: boolean;
}
function TodoApp() {
const [todos, setTodos] = useState<Todo[]>([]);
const [input, setInput] = useState("");
const addTodo = () => {
if (input.trim() === "") return;
const newTodo: Todo = {
id: Date.now(),
text: input,
completed: false,
};
setTodos([...todos, newTodo]);
setInput("");
};
const toggleTodo = (id: number) => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
),
);
};
const deleteTodo = (id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<div>
<h1>My Todos</h1>
<div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && addTodo()}
placeholder="Add a new todo..."
/>
<button onClick={addTodo}>Add</button>
</div>
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
</div>
);
}
interface TodoItemProps {
todo: Todo;
onToggle: (id: number) => void;
onDelete: (id: number) => void;
}
function TodoItem({ todo, onToggle, onDelete }: TodoItemProps) {
return (
<li
style={{
textDecoration: todo.completed ? "line-through" : "none",
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
}このアプリでは以下の概念が使われています:
- JSX: UI を宣言的に記述
- コンポーネント:
TodoAppとTodoItemに分割 - Props:
TodoItemにtodoとコールバック関数を渡す - State:
todosとinputの状態管理 - イベントハンドリング:
onClick,onChange,onKeyPress
よくあるエラーと解決策
React 初心者が最初に出くわしやすいエラーと、その原因・解決法を紹介します。
エラー1: "Objects are not valid as a React child"
症状:
function Bad() {
const user = { name: "Alice" };
return <div>{user}</div>; // ❌ エラー
}原因: オブジェクト全体を JSX に直接埋め込もうとしている
解決策:
function Good() {
const user = { name: "Alice" };
return <div>{user.name}</div>; // ✅ オブジェクトのプロパティを指定
}エラー2: "Cannot update a component while rendering a different component"
症状: State 更新が無限ループして警告が出る
原因: コンポーネント内で直接 setState を呼んでいる(useEffect の外で)
解決策:
import { useState, useEffect } from "react";
function Good() {
const [count, setCount] = useState(0);
useEffect(() => {
// ここで State を更新する
setCount(1);
}, []); // 空の依存配列で、マウント時に1度だけ実行
return <div>{count}</div>;
}エラー3: "Each child in a list should have a unique 'key' prop"
症状: リストをレンダリングすると警告が出る
原因: リスト要素に key props を指定していない
解決策:
// ❌ 間違い: インデックスを key にする(リストが並び替わると問題になる)
{
todos.map((todo, index) => <TodoItem key={index} todo={todo} />);
}
// ✅ 正しい: 一意の ID を key にする
{
todos.map((todo) => <TodoItem key={todo.id} todo={todo} />);
}エラー4: "Cannot read property 'X' of undefined"
症状: Props を受け取るコンポーネントで、Props が undefined
原因: Props の型チェックをしていない、または初期値を設定していない
解決策:
// TypeScript で型定義
interface UserProps {
name?: string; // オプショナルプロパティ
}
function User({ name = "Guest" }: UserProps) {
return <div>{name}</div>;
}よくある質問
Q1: Props と State はどう使い分けるの?
簡単な法則:
- データが変わる可能性があるか? → State を使う
- 親コンポーネントで管理されているか? → Props で受け取る
例えば、フォームの入力値は時間とともに変わるので State です。一方、ユーザー情報は親が管理していて、子は受け取るだけなので Props です。
Q2: State を頻繁に更新すると遅くなる?
React は効率的に設計されており、State の更新はバッチ処理されます。通常のアプリケーションでは、State の更新頻度によるパフォーマンス問題はほぼ発生しません。
ただし、非常に大きなリスト(1000 個以上の要素)を扱う場合は、仮想化(virtualization)を検討します。
Q3: 子コンポーネントから親の State を変更できる?
直接は不可です。代わりに:
// 親が子に関数を Props として渡す
function Parent() {
const [count, setCount] = useState(0);
return <Child onIncrement={() => setCount(count + 1)} />;
}
function Child({ onIncrement }) {
return <button onClick={onIncrement}>Increment</button>;
}このパターンで、子が親の State を間接的に変更できます。
Q4: JSX で JavaScript のロジックを書けるの?
はい、{} の中に書けます:
function Example({ items }) {
return (
<div>
{items.filter((item) => item.active).length > 0 ? (
<p>Active items: {items.filter((item) => item.active).length}</p>
) : (
<p>No active items</p>
)}
</div>
);
}ただし、複雑なロジックはコンポーネントの外に関数として抽出する方が良いです。
Q5: 複数の State を持つことはできる?
**もちろんです。**1 つのコンポーネントで複数の useState を使用できます:
function Form() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [submitted, setSubmitted] = useState(false);
// 複数の State を管理
}デバッグのコツ
方法1: console.log を活用
Props や State の値を確認する最も基本的な方法です:
function Component({ name }: { name: string }) {
const [count, setCount] = useState(0);
console.log("Props:", name);
console.log("State:", count);
return (
<div>
{name} - {count}
</div>
);
}ポイント: レンダリングが何度も実行される可能性があるため、console.log が複数回出力されることは正常です。
方法2: React DevTools を使う
ブラウザに React DevTools 拡張機能をインストールすると、Props と State をビジュアルで確認できます:
- ブラウザの DevTools で「Components」タブを開く
- 確認したいコンポーネントをクリック
- 右パネルで Props と State をリアルタイム確認
- State を変更してテストすることも可能
方法3: ESLint で早期発見
eslint-plugin-react-hooks を使うと、一般的な React の問題を事前に検出できます:
{
"extends": ["plugin:react-hooks/recommended"]
}これで、以下のような問題が自動で検出されます:
- Hook の使い方の誤り
- 依存配列の不足
- Props の誤用
React 開発での個人的なハマりポイント
React を学ぶ上で、特に注意が必要な点を3つ紹介します:
ハマりポイント1: State は即座には更新されない
State の更新は非同期です。つまり、setState の直後に State を使用しても、古い値が取得されます:
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // まだ古い値が出力される(count + 1 ではない)
};
return <button onClick={handleClick}>Count: {count}</button>;
}対策: State の値が更新されたことをテストするには useEffect を使う:
useEffect(() => {
console.log("Count updated:", count);
}, [count]);ハマりポイント2: Props は親からの「指示」(子から変更不可)
子が Props を変更することはできません。よくある誤解をまとめます:
アンチパターン1: Props を直接変更
// ❌ 間違い
function UserCard(props) {
props.name = "Bob"; // これは無視されるか、エラーになる
return <h1>{props.name}</h1>;
}アンチパターン2: Props を直接 State に代入
// ❌ 間違い:親が Props を更新してもローカル State は更新されない
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount); // initialCount の値で固定
return <div>{count}</div>;
}正解: 親から関数を受け取る
// ✅ 正しい:親が updateName 関数を子に渡す
function Parent() {
const [name, setName] = useState("Alice");
return <Child name={name} onNameChange={setName} />;
}
function Child({ name, onNameChange }) {
return (
<button onClick={() => onNameChange("Bob")}>Change name: {name}</button>
);
}このパターンで、子は親の State を間接的に操作できます。
ハマりポイント3: リスト要素に配列のインデックスを key に使わない
インデックスを key にすると、リストが並び替わった時に予期しない動作が発生します:
// ❌ このような書き方は避ける
{
items.map((item, index) => <Item key={index} value={item} />);
}
// ✅ 各要素に一意な ID がある場合、それを key に
{
items.map((item) => <Item key={item.id} value={item} />);
}学習ロードマップ
React の基本を習得した後、次に学ぶべき概念があります:
- 複数の State 管理ライブラリ - Redux、Zustand、Jotai など
- 副作用の管理 - useEffect、useCallback などの Hook
- パフォーマンス最適化 - useMemo、React.memo の活用
- フォーム管理 - React Hook Form などのライブラリ
- テスト - Jest、React Testing Library での単体テスト
まずは Props と State の流れをしっかり理解することが、後の学習をスムーズにします。
ベストプラクティス
React 開発を進める上で心がけるべき重要なポイント:
- コンポーネントの責任は単一にする - 1つのコンポーネントは1つのことをよくやるべき
- Props は親から子へ(単一方向) - 逆向きのデータフローを避ける
- State は必要最小限に - どのコンポーネントが State を持つべきかを慎重に考える
- 再利用性を意識する - 他の場所でも使えるコンポーネント設計を心がける
- デバッグ時は React DevTools を活用 - Props と State の変化を追跡できます
まとめ
本記事では、React の基本概念である JSX、コンポーネント、Props、State について深く学びました。これらは React の全機能の基礎となり、後で学ぶ Hook やデータ管理も、これらの概念の上に構築されています。
学んだ概念の関係性:
JSX(HTMLのような構文)
↓
コンポーネント(JSX を返す関数)
↓
Props(親から子へ)& State(コンポーネント内)
↓
単一方向フロー(データは常に上から下へ)
重要な3つの原則:
- Props は読み取り専用 - 子は親から受け取った Props を変更できない
- State は必要な場所で - State を持つべきコンポーネントを意識的に選ぶ
- データは上から下へ - 逆向きの通信はコールバック関数で実現
最初は複雑に感じるかもしれませんが、実際にコードを書きながら試す中で、これらの概念が自然と身につきます。「エラーが出た → よくあるエラーセクションを見る → 修正する」 このループを何度も経験することが、最も効果的な学習方法です。
実装してみよう
本記事で学んだ概念を実装してみるのが最も効果的です。以下のステップで進めてください:
-
Create React App で新規プロジェクト作成
npx create-react-app my-react-app cd my-react-app npm start -
シンプルなコンポーネントから始める
- ボタンをクリックするとカウンターが増えるコンポーネント
- Props で名前を受け取り、挨拶を表示するコンポーネント
-
デバッグを体験する
- React DevTools を開いて、State の変化を追跡
- console.log で Props を確認
-
Todo アプリを実装する
- 本記事で紹介した Todo アプリを自分で実装
- 動作確認、デバッグを通じて学習を深化
学習の鍵は「動かして、壊して、直す」です。エラーが出たら、本セクションの「よくあるエラーと解決策」を参照してください。
関連記事
- 技術
TypeScript入門 - Reactで使う主な型定義
ReactでTypeScriptを使うときの基本的な型定義をまとめた入門記事。関数コンポーネント、Props、useStateなどのHooks、イベントハンドラ、カスタムフック、ユーティリティ型まで実例コード付きで解説します。
TypeScriptReactフロントエンド - 技術
Feature-Sliced Design と Packaged by Feature: スケーラブルなアーキテクチャの設計
コードが増えるほど開発が遅くなる原因はファイル整理にある。フロントエンドの Feature-Sliced Design とバックエンドの Packaged by Feature を、3層構造やマイクロサービス分割の実例とともに解説する。
アーキテクチャフロントエンドTypeScript - 技術
12KBの衝撃。Honoで始める爆速Web開発、ExpressからCloudflare Workersまで
わずか12KBの軽量WebフレームワークHonoを実際に試した記録。Hello WorldからCRUD API、Node.js・Cloudflare Workers・Denoでの動作、Zod連携やミドルウェア、Expressからの移行戦略とエッジ環境での性能まで具体的に解説します。
HonoTypeScriptCloudflare
