1. 👋 はじめに
前回は生TypeScriptでのDOM操作を学びました。そして最後にこんな「辛さ」を体感しましたね。
データが変わるたびに「全消し→全再描画」という力技…
「変更された部分だけ自動で更新してほしい!」
その願いを叶えるのが React です🚀
この記事を読めば:
- ✅ なぜReactが必要かがわかる
- ✅ TSXコンポーネントの書き方がわかる
- ✅ Props・Stateに型をつける方法がわかる
- ✅
useStateの基本が使えるようになる
2. 🤔 なぜReactが必要なのか?
DOM操作の問題点をおさらい
前回のToDoリストを思い出してください。
❌ TypeScriptでは:
データ(配列) 画面(HTML)
│ │
│ 変更があるたびに │
│──────────────────→ │ 全消し
│ │ ↓
│ │ 全再描画
│ │
手動で同期し続ける必要がある 😩
✅ Reactの世界:
データ(State) 画面(JSX)
│ │
│ データが変わると │
│──────────────────→ │ 自動で差分だけ更新!
│ │
Reactが自動で同期してくれる 🎉
Reactの3つの特徴
| 特徴 | 説明 |
|---|---|
| 🧩 コンポーネント | UIを部品(コンポーネント)に分割して再利用できる |
| 🔄 自動更新 | データ(State)が変わると、関係する部分だけ自動で画面を更新 |
| 🌳 仮想DOM | 実際のDOMを操作する前に差分を計算して最小限の更新に抑える |
3. 🛠️ 開発環境を準備しよう
Viteで新しいプロジェクトを作成
# React + TypeScript のプロジェクトを作成
npm create vite@latest my-app -- --template react-ts
# プロジェクトフォルダに移動
cd my-app
# 依存パッケージをインストール
npm install
# 開発サーバーを起動
npm run dev
生成されるファイル構造
my-app/
├── src/
│ ├── App.tsx ← メインのコンポーネント
│ ├── main.tsx ← エントリーポイント
│ └── index.css ← グローバルCSS
├── index.html
├── package.json
├── tsconfig.json ← TypeScriptの設定
└── vite.config.ts ← Viteの設定
4. 🧩 コンポーネントとは?
一言で言うと
コンポーネントとは「UIの部品」です。
ボタン・カード・ナビゲーションバーなど、
画面を小さな部品に分割して組み合わせます。
コンポーネントのイメージ
【コンポーネントに分割する前】 【コンポーネントに分割した後】
<div> <App>
<header>...</header> <Header />
<main> <Main>
<div class="card">...</div> <Card />
<div class="card">...</div> <Card />
<div class="card">...</div> <Card />
</main> </Main>
<footer>...</footer> <Footer />
</div> </App>
→ 同じカードを3回書く → Card を3回使いまわす!
最初のTSXコンポーネント
// src/components/Greeting.tsx
// Greetingを返すコンポーネント
const Greeting = () => {
return (
<div>
<h1>こんにちは!</h1>
<p>Reactへようこそ。</p>
</div>
);
};
export default Greeting;
// src/App.tsx で使う
// Greetingをインポート
import Greeting from "./components/Greeting";
const App = () => {
return (
<div>
<Greeting /> {/* コンポーネントを使う */}
</div>
);
};
export default App;
💡 FC について
Reactのコードで
FC(FunctionComponent)という型を見かけることがあります。
これはReactが提供する「関数コンポーネントであること」を示す型です。// FC を使う書き方(従来スタイル) import { FC } from "react"; const Greeting: FC = () => <h1>こんにちは</h1>;ただし React 18以降は
FCなしでシンプルに書くスタイルが主流 になっています。
このシリーズでもFCを使わない現代スタイルで統一します。
Reactのルール
💡 JSX と TSX について
ReactでJavaScriptを使う場合は JSX(.jsx)、
TypeScriptを使う場合は TSX(.tsx)と呼びます。
このシリーズではTypeScript前提なのでTSXを使いますが、
ルール自体はJSX・TSX共通です。
// ✅ ルール① 必ず1つの要素を返す
const Good = () => {
return (
<div> {/* 1つのルート要素 */}
<h1>タイトル</h1>
<p>本文</p>
</div>
);
};
// ✅ フラグメント(<>)で余分なdivを避ける
const WithFragment = () => {
return (
<> {/* divを使わずに複数要素を返せる */}
<h1>タイトル</h1>
<p>本文</p>
</>
);
};
// ✅ ルール② 自己閉じタグが必要
const SelfClose = () => {
return (
<div>
<img src="photo.jpg" alt="写真" /> {/* / が必要! */}
<br />
</div>
);
};
// ✅ ルール③ class ではなく className を使う
const WithClass = () => {
return <div className="card">カード</div>;
// ↑ HTMLのclassではなくclassName!
};
5. 📨 Props:コンポーネントに値を渡す
Propsとは?
Props(プロップス)とは「コンポーネントに渡す引数」です。
親コンポーネントから子コンポーネントへデータを渡すときに使います。
Propsに型をつける
// src/components/UserCard.tsx
// Propsの型を type で定義する(現代スタイル)
type UserCardProps = {
name: string;
email: string;
age: number;
isAdmin?: boolean; // ? をつけると省略可能(オプショナル)
};
// 型を引数に指定する
const UserCard = ({ name, email, age, isAdmin = false }: UserCardProps) => {
return (
<div className="card">
<h2>{name}</h2>
<p>メール:{email}</p>
<p>年齢:{age}歳</p>
{isAdmin && <span className="badge">管理者</span>}
</div>
);
};
export default UserCard;
// src/App.tsx で使う
import UserCard from "./components/UserCard";
const App = () => {
return (
<div>
{/* Propsを渡す */}
<UserCard name="田中 太郎" email="taro@example.com" age={25} isAdmin={true} />
<UserCard name="鈴木 花子" email="hanako@example.com" age={30} />
{/* isAdmin を省略 → デフォルトの false が使われる */}
</div>
);
};
よく使うPropsの型パターン
type ExampleProps = {
// 基本的な型
title: string;
count: number;
isVisible: boolean;
// 配列
items: string[];
users: { id: number; name: string }[];
// 関数(イベントハンドラー)
onClick: () => void;
onChange: (value: string) => void;
// 子コンポーネント(children)
children: React.ReactNode;
// オプショナル
description?: string;
};
children Props:コンポーネントの中身を渡す
// src/components/Card.tsx
type CardProps = {
title: string;
children: React.ReactNode; // 中身を受け取る
};
const Card = ({ title, children }: CardProps) => {
return (
<div className="card">
<h2 className="card-title">{title}</h2>
<div className="card-body">
{children} {/* 受け取った中身をここに表示 */}
</div>
</div>
);
};
export default Card;
// 使う側
<Card title="ユーザー情報">
<p>田中 太郎</p>
<p>taro@example.com</p>
</Card>
<Card title="お知らせ">
<ul>
<li>新機能が追加されました</li>
<li>メンテナンスのお知らせ</li>
</ul>
</Card>
6. 🔄 State:コンポーネントの状態管理
Stateとは?
State(ステート)とは「コンポーネントが持つ内部データ」です。
Stateが変わると、Reactが自動でそのコンポーネントを再レンダリングします。
Props:親から受け取る・変更できない(読み取り専用)
State:自分が持つ・自分で変更できる
useState の基本
import { useState } from "react";
const Counter = () => {
// useState<型>(初期値) → [現在の値, 値を更新する関数]
const [count, setCount] = useState<number>(0);
return (
<div>
<p>カウント:{count}</p>
<button onClick={() => setCount(count + 1)}>増やす</button>
<button onClick={() => setCount(count - 1)}>減らす</button>
<button onClick={() => setCount(0)}>リセット</button>
</div>
);
};
【useState の仕組み】
const [count, setCount] = useState<number>(0);
↑ ↑ ↑
現在の値 更新する関数 初期値(0)
setCount(5) を呼ぶ
↓
count が 5 に変わる
↓
Reactがコンポーネントを自動で再レンダリング
↓
画面が新しい値(5)で更新される!
💡 TypeScriptの知恵袋:型推論(型の省略)
初期値が
0(数値)・""(文字列)・false(真偽値)のように明確な場合、
TypeScriptが自動で型を推測してくれるため、<number>などは省略してもOKです!const [count, setCount] = useState(0); // 自動的に number 型になる const [name, setName] = useState(""); // 自動的に string 型になる const [flag, setFlag] = useState(false); // 自動的に boolean 型になるただし 空の配列を初期値にするとき は省略できません。
「何が入る配列か」をTypeScriptが判断できないため、
必ずジェネリクスで型を教えてあげる必要があります。const [items, setItems] = useState([]); // ❌ never[] 型になってしまう const [items, setItems] = useState<Todo[]>([]); // ✅ Todo[] 型として扱われる
よく使う State のパターン
import { useState } from "react";
const Examples = () => {
// 文字列のState
const [name, setName] = useState("");
// 真偽値のState(表示/非表示の切り替えに便利)
const [isOpen, setIsOpen] = useState(false);
// 配列のState
const [items, setItems] = useState<string[]>([]);
// オブジェクトのState(type で別に定義するとスッキリ)
type User = {
name: string;
age: number;
};
const [user, setUser] = useState<User>({
name: "",
age: 0,
});
return (
<div>
{/* 入力フォームとStateを連動させる */}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力"
/>
<p>入力中:{name}</p>
{/* 表示/非表示の切り替え */}
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? "閉じる" : "開く"}
</button>
{isOpen && <div className="panel">パネルの中身</div>}
</div>
);
};
⚠️ Stateを直接変更してはいけない
// Stateの宣言(初期値は空の配列)
const [items, setItems] = useState<string[]>([]);
// どこかでデータが追加されて items = ["A", "B"] になっているとする
// ❌ 絶対にやってはいけない:直接変更する
items.push("C"); // ❌ 直接変更 → 画面が更新されない!
items[0] = "Z"; // ❌ 直接変更 → 画面が更新されない!
💡 なぜ直接変更してはいけないのか?
ReactはStateが「新しいオブジェクトに変わった」ことで再レンダリングを判断します。
直接変更すると「同じオブジェクト」のままなので、変化に気づけません。
必ず新しい配列・オブジェクトを作ってsetterに渡しましょう。
✅ 配列Stateの正しい操作方法
① 要素を追加する
// items = ["A", "B"] の状態から追加する例
// 「...items」は「今の配列を全部展開する」という意味
// [...items, "C"] = ["A", "B", "C"] という新しい配列を作る
setItems([...items, "C"]);
// 実行前:["A", "B"]
// 実行後:["A", "B", "C"]
💡
...(スプレッド構文)とは?
配列の中身をバラバラに展開する記法です。
[...items, "C"]は「今の配列の全要素 + "C"」という新しい配列を作ります。
② 特定の要素を削除する
// items = ["A", "B", "C"] の状態から "A" を削除する例
// filter:条件に「一致しない」要素だけを残した新しい配列を返す
// 「"A" 以外の要素だけを残す」= "A" を削除する
setItems(items.filter((i) => i !== "A"));
// 実行前:["A", "B", "C"]
// 実行後:["B", "C"]("A"だけ消えた)
💡
filterとは?
配列の各要素を1つずつチェックして、
条件がtrueの要素だけを集めた新しい配列を返します。
i !== "A"は「"A" ではない」という条件なので、"A" 以外が残ります。
③ 特定の要素を更新する
// items = ["A", "B", "C"] の状態から "A" を "Z" に変更する例
// map:配列の各要素を変換した新しい配列を返す
// 「"A" なら "Z" に変換・それ以外はそのまま」
setItems(items.map((i) => (i === "A" ? "Z" : i)));
// 実行前:["A", "B", "C"]
// 実行後:["Z", "B", "C"]("A"だけ"Z"に変わった)
💡
mapとは?
配列の各要素を1つずつ変換して、新しい配列を返します。
i === "A" ? "Z" : iは「"A" なら "Z" を返す・それ以外は元の値のまま」という意味です。
3つの操作をまとめて整理
| やりたいこと | 使うメソッド | イメージ |
|---|---|---|
| 追加 | [...items, 新要素] |
今の配列をコピーして末尾に追加 |
| 削除 | items.filter(条件) |
条件に合う要素だけを残す |
| 更新 | items.map(変換) |
各要素を変換して新しい配列を作る |
7. 🏗️ 実践:ReactでToDoリストを作る
前回の生DOM操作と比べてみましょう!
// src/App.tsx
import { useState } from "react";
// 型定義
type Todo = {
id: number;
text: string;
completed: boolean;
};
// ✅ コンポーネントの外側で定義する(再レンダリングでリセットされない)
// ❌ コンポーネントの内側に書くと、Stateが変わるたびに let nextId = 1 に
// リセットされてしまい、2つ目以降のIDがすべて同じになるバグが発生する!
let nextId = 1;
const App = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [inputText, setInputText] = useState("");
// ToDoを追加する
const addTodo = (): void => {
if (!inputText.trim()) return;
const newTodo: Todo = {
id: nextId++,
text: inputText.trim(),
completed: false,
};
setTodos([...todos, newTodo]);
setInputText("");
};
// 完了/未完了を切り替える
const toggleTodo = (id: number): void => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
// ToDoを削除する
const deleteTodo = (id: number): void => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<div className="app">
<h1>📝 ToDoリスト</h1>
{/* 入力エリア */}
<div className="input-area">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTodo()}
placeholder="やることを入力..."
/>
<button onClick={addTodo}>追加</button>
</div>
{/* ToDoリスト */}
<ul>
{todos.map((todo) => (
<li
key={todo.id}
className={todo.completed ? "completed" : ""}
>
<span>{todo.text}</span>
<button onClick={() => toggleTodo(todo.id)}>
{todo.completed ? "戻す" : "完了"}
</button>
<button onClick={() => deleteTodo(todo.id)}>削除</button>
</li>
))}
</ul>
{/* 件数表示 */}
<p>{todos.filter((t) => !t.completed).length}件が未完了</p>
</div>
);
};
export default App;
💡 コラム:Reactが速い理由(仮想DOM)
実際のブラウザの画面(リアルDOM)を書き換える処理は非常に重い処理です。
Reactはまずメモリ上の軽い 「仮想DOM」 で差分を計算し、
本当に変わったピンポイントな部分だけをリアルDOMに適用します。【前回の生DOM操作】 【Reactの仮想DOM】 データ変更 データ変更 ↓ ↓ 全要素を削除 仮想DOM上で差分を計算 ↓ ↓ 全要素を再生成 変わった部分だけを特定 ↓ ↓ リアルDOMに反映(重い!) その部分だけリアルDOMに反映(速い!)
生DOM操作との比較
| 生TypeScript(第三回) | React(第四回) | |
|---|---|---|
| データの管理 | 配列を手動で管理 |
useState で管理 |
| 画面の更新 |
innerHTML = "" で全消し→再描画 |
Stateが変わると自動で差分更新 |
| イベント登録 |
addEventListener を手動で設定 |
JSX内に onClick を直接書く |
| コード量 | 多い・複雑になりやすい | 少ない・読みやすい |
「全消し→再描画」という力技が、
setTodos([...todos, newTodo])の1行で済むようになりました! 🎉
8. 🎯 まとめ
| 概念 | 一言で言うと |
|---|---|
| 🧩 コンポーネント | UIの部品・関数として定義してタグのように使う |
| 📝 TSX | TypeScript + JSX・HTMLに似た記法でUIを書く |
| 📨 Props | 親から子へデータを渡す・type で型を定義 |
| 🔄 State | コンポーネントの内部データ・変わると自動で画面が更新 |
| 🪝 useState |
[値, setter] のペアを返すReactのフック |
Reactの核心:
「データ(State・Props)が変わると、Reactが自動で画面を更新する」
この仕組みを理解すると、Reactの設計がスムーズに理解できます💪
次回は React × TypeScriptの実践 を解説します!
useEffect でのAPI連携・フォームのバリデーション・
コンポーネントの分割設計を学びます🌟
💬 質問や感想があれば、コメント欄でお気軽にどうぞ!
👍 役に立ったら、いいね&ストックをお願いします!
🎓 ここまで読んでくださって、本当にありがとうございました!
🔗 シリーズ記事
- 【第一回】Webページの仕組み・HTML基礎
- 【第二回】CSSで見た目を整える
- 【第三回】TypeScriptでインタラクションを作る
- 【第四回】React × TypeScriptの基礎(この記事)
- 【第五回】React × TypeScriptの実践(近日公開)
- 【第六回】まとめ・開発環境・次のステップ(近日公開)