React Guide for TypeScript Developers

React入門ガイド
— TypeScriptは分かる人向け

TypeScriptの文法は知っているが、Reactは触ったことがないという前提でまとめた入門資料です。 JSXの読み方からコンポーネント、State、イベント処理、フォーム、副作用(useEffect)まで、 「簡単なWebアプリを1つ作れる」ところまでをゴールにしています。 Next.jsなどのフレームワークやサーバーサイドの話は扱わず、React本体の機能に絞っています。

React 19系 TypeScript フレームワーク不使用(Vite想定)

1Reactとは何か

最初に、Reactが何を解決するライブラリなのかを押さえます。

Reactは、UI(画面)を作るためのJavaScript/TypeScriptのライブラリです。 Angularのような「全部入りフレームワーク」ではなく、あくまで「画面をどう組み立てて、どう更新するか」に特化しています。

素のTypeScript + DOM操作でアプリを作ると、「データが変わったらどの要素を書き換えるか」を自分で管理する必要があります。 Reactでは、代わりに「今のデータがこうならUIはこう見えるはず」という宣言的な書き方をします。 データ(stateと呼びます)が変わると、Reactが自動的に画面の差分だけを再描画してくれます。

考え方の転換

「クリックされたらこの要素のテキストを書き換える」ではなく、 「このデータが変わったら、画面はこう表示される」という関数として画面を定義する、という発想がReactの中心です。

Reactの世界には次のような基本要素があります。それぞれ後の章で詳しく説明します。

用語役割
コンポーネント画面の部品。UIを返すTypeScriptの関数
JSXTypeScript内にHTMLに似た構文でUIを書ける拡張構文
props親コンポーネントから子コンポーネントに渡す値(引数のようなもの)
stateコンポーネントが内部で保持し、変化すると再描画のきっかけになるデータ
HooksuseStateuseEffectなど、useで始まる関数群。関数コンポーネントに状態や副作用の機能を追加する

2開発環境を作る

React単体(Next.jsなどのフレームワークなし)で始める最短ルートです。

以前は Create React App がよく使われていましたが、現在は非推奨(deprecated)になっています。 フレームワークを使わずReact単体で始める場合、現在はViteというビルドツールを使うのが一般的です。 Viteはあくまでビルド・開発サーバーのツールであり、Next.jsのようにルーティングやサーバーサイド機能を提供する「フレームワーク」ではありません。

terminal
# TypeScript + React のプロジェクトを作成
npm create vite@latest my-app -- --template react-ts

# プロジェクトフォルダに移動して依存関係をインストール
cd my-app
npm install

# 開発サーバーを起動
npm run dev

これで src/App.tsx というファイルが生成されます。ここが最初に編集するファイルです。 以降のコード例はすべて、この .tsx(TypeScript + JSX)ファイルの中身だと思って読んでください。

3コンポーネントとJSX

Reactアプリは「コンポーネント」という部品を組み合わせて作ります。

コンポーネントとは、UIを返す関数です。名前は必ず大文字始まりにします(小文字だと通常のHTMLタグと区別できません)。

App.tsx
function Hello() {
  return <h1>Hello, React!</h1>;
}

export default function App() {
  return (
    <div>
      <Hello />
      <p>これは別のコンポーネントです</p>
    </div>
  );
}

関数の中で return しているHTMLのような記述がJSXです。 JSXはTypeScriptの構文の中にマークアップを埋め込んだもので、コンパイル時に React.createElement(...) 相当のコードに変換されます。 JSXにはいくつか通常のHTMLと違うルールがあります。

  • タグは必ず閉じる(<br /> のように自己終了タグにする)
  • 1つの関数は1つのルート要素しか返せない。複数返したい場合は <div> や空タグ <>...</>(Fragment)で囲む
  • CSSクラスは class ではなく className を使う
  • { }(波括弧)の中にはTypeScriptの式を書ける
App.tsx — JSXの中でTypeScriptの式を使う
const userName: string = "Kosuke";

function Greeting() {
  return <h1>Hello, {userName}!</h1>;
  // { } の中は式として評価される。変数、関数呼び出し、三項演算子などが使える
}
.tsx というファイル拡張子

JSXをTypeScriptで書く場合、ファイル拡張子は .tsx にします。通常の .ts ではJSX構文を解釈できません。

4Props — 親から子へデータを渡す

propsは「コンポーネントの引数」だと考えると理解しやすいです。

コンポーネントは関数なので、引数を受け取れます。この引数のことをpropsと呼びます。 TypeScriptでは、propsの型を interfacetype で定義するのが一般的です。

App.tsx
interface UserCardProps {
  name: string;
  age: number;
}

function UserCard({ name, age }: UserCardProps) {
  return (
    <div>
      <p>名前: {name}</p>
      <p>年齢: {age}</p>
    </div>
  );
}

export default function App() {
  return (
    <UserCard name="太郎" age={28} />
  );
}

親(App)が <UserCard name="太郎" age={28} /> のように属性としてデータを渡し、 子(UserCard)はそれを引数として受け取ります。文字列以外の値(数値・真偽値・オブジェクト・関数など)を渡すときは { } を使います。

propsは読み取り専用

子コンポーネントの中で受け取ったpropsの値を直接書き換えることはできません(TypeScript的にも実行時的にもNG)。 値を変えたい場合は、後述するstateとして親側で管理し、更新用の関数をpropsとして渡します。

children という特別なpropsもあります。タグで囲んだ中身がこれに入ります。

App.tsx
interface CardProps {
  children: React.ReactNode;
}

function Card({ children }: CardProps) {
  return <div className="card">{children}</div>;
}

// 使う側
<Card>
  <p>これがchildrenになります</p>
</Card>

5State — useStateで「変化するデータ」を持つ

stateは、コンポーネントが自分で持つ、時間とともに変化するデータです。

通常のローカル変数を書き換えても画面は再描画されません。画面を更新したいデータは useState というHookで管理します。useStateは「現在の値」と「値を更新する関数」のペアを返します。

Counter.tsx
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState<number>(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      クリック回数: {count}
    </button>
  );
}
  • useState(0) の引数が初期値。useState<number>で型を明示できる(初期値から型推論される場合は省略可)
  • count は現在の値、setCount はそれを更新するための関数
  • setCount(...) を呼ぶと、Reactはコンポーネントを再実行して画面を更新する
stateを直接書き換えない

count = count + 1 のように直接代入しても画面は更新されません。必ず setCount(setから始まる更新関数)を呼びます。

また、更新後の値が直前の値に依存する場合は、関数を渡す形が安全です: setCount(prev => prev + 1)。 同じイベント内で複数回更新する場合や非同期処理が絡む場合に、古い値を参照してしまう不具合を防げます。

stateはオブジェクトや配列も持てますが、その場合も直接書き換えず、新しいオブジェクト・配列を作って渡すのが鉄則です。

Profile.tsx
interface Profile {
  name: string;
  age: number;
}

const [profile, setProfile] = useState<Profile>({ name: "太郎", age: 28 });

// NG: profile.age++ のような直接変更
// OK: 新しいオブジェクトを作って置き換える
setProfile({ ...profile, age: profile.age + 1 });

6イベント処理

クリックや入力などのイベントは、JSXの属性として関数を渡す形で扱います。

HTMLでは onclick="..." のように文字列で書きますが、JSXでは onClick={ハンドラ関数} のように、関数そのものを渡します(キャメルケースになる点にも注意)。

App.tsx
function App() {
  function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
    console.log("clicked", event.currentTarget);
  }

  return <button onClick={handleClick}>押す</button>;
}
関数を「呼び出さず」に渡す

onClick={handleClick()} と書くと、レンダリング時に即実行されてしまいます。 クリック時に実行したい場合は onClick={handleClick}(括弧なし)にします。 引数を渡したい場合はアロー関数で包みます: onClick={() => handleDelete(id)}

Reactの主なイベントの型はTypeScriptで次のように書けます。

イベント
onClickReact.MouseEvent<HTMLButtonElement>
onChange(input)React.ChangeEvent<HTMLInputElement>
onSubmit(form)React.FormEvent<HTMLFormElement>
onKeyDownReact.KeyboardEvent<HTMLInputElement>

7条件付きレンダリング

JSXには専用の「if構文」はありません。TypeScriptの通常の式を使います。

条件によって表示を切り替える3つの方法
// 1. 三項演算子(if/elseが必要なとき)
<div>
  {isLoggedIn ? <p>ようこそ</p>> : <p>ログインしてください</p>}
</div>

// 2. && 演算子(elseが不要なとき)
<div>
  {errorMessage && <p className="error">{errorMessage}</p>}
</div>

// 3. 関数内でif文を使い、返す要素をあらかじめ決める
function Status({ isOnline }: { isOnline: boolean }) {
  if (isOnline) {
    return <span>オンライン</span>;
  }
  return <span>オフライン</span>;
}
&& を数値と一緒に使うときの罠

{count && <p>...</p>} のように書くと、count0 のときに画面に「0」という文字がそのまま表示されてしまうことがあります。 {count > 0 && ...} のように、必ず真偽値になる条件式にしましょう。

8リストのレンダリングとkey

配列からUIを生成するときは map() を使います。

TodoList.tsx
interface Todo {
  id: number;
  text: string;
}

function TodoList({ todos }: { todos: Todo[] }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}
keyは必須

リストの各要素には key という特別なpropsを付けます。 Reactはこの key を使って「どの要素が追加・削除・並び替えされたか」を判定します。 データのID(todo.idなど)を使うのが基本で、配列のインデックス(index)を使うのは、 要素の順序が変わらない場合に限り許容されます(並び替えや削除があるとバグの原因になります)。

9フォームの扱い(制御コンポーネント)

Reactでは、入力欄の値もstateとして管理するのが基本パターンです。

入力値をstateに保存し、value にそのstateを渡すことで、 「入力欄の値 = state」という状態を常に保つ書き方を制御コンポーネントと呼びます。

NameForm.tsx
import { useState } from "react";

function NameForm() {
  const [name, setName] = useState<string>("");

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setName(e.target.value);
  }

  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault(); // ページ再読み込みを防ぐ
    alert(`送信された名前: ${name}`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={name} onChange={handleChange} />
      <button type="submit">送信</button>
    </form>
  );
}

ポイントは3つです。value に必ずstateを渡すこと、onChangeで毎回stateを更新すること、 フォーム送信時は e.preventDefault() でブラウザのデフォルトの再読み込みを止めることです。

10useEffect — 副作用を扱う

レンダリングそのものとは関係のない処理(API通信、タイマー、購読など)を行うためのHookです。

コンポーネント関数は「渡されたデータからUIを計算するだけ」であるべきで、 データ取得やタイマー設定のような「外の世界とのやり取り」は副作用(side effect)と呼ばれ、useEffectの中に書きます。

ClockDisplay.tsx
import { useState, useEffect } from "react";

function ClockDisplay() {
  const [time, setTime] = useState<Date>(new Date());

  useEffect(() => {
    const timerId = setInterval(() => {
      setTime(new Date());
    }, 1000);

    // クリーンアップ: コンポーネントが消えるときにタイマーを止める
    return () => clearInterval(timerId);
  }, []); // 依存配列が空 = マウント時に1回だけ実行

  return <p>現在時刻: {time.toLocaleTimeString()}</p>;
}

useEffectの第2引数(依存配列)の意味を理解しておくと事故が減ります。

依存配列実行タイミング
useEffect(fn)(省略)毎回の再レンダリングのたびに実行
useEffect(fn, [])最初の1回(マウント時)だけ実行
useEffect(fn, [count])最初の1回、および count が変化するたびに実行
UserProfile.tsx — APIからデータを取得する典型例
interface User {
  id: number;
  name: string;
}

function UserProfile({ userId }: { userId: number }) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    let cancelled = false;

    fetch(`/api/users/${userId}`)
      .then((res) => res.json())
      .then((data: User) => {
        if (!cancelled) setUser(data);
      });

    return () => { cancelled = true; }; // 古いリクエストの結果を無視する
  }, [userId]); // userIdが変わるたびに再取得

  if (!user) return <p>読み込み中...</p>;
  return <p>{user.name}</p>;
}
useEffectは「最終手段」

propsやstateから計算できる値(合計や絞り込み結果など)をわざわざuseEffectとstateで持つ必要はなく、 レンダリング中に直接計算すれば十分です。useEffectは、外部システムとの同期(通信・DOM操作・タイマー・購読など)が本当に必要な場合に使います。

11useRef — 再レンダリングを起こさない値・DOM参照

stateと違い、値が変わっても再描画を引き起こさない「箱」です。

FocusInput.tsx — DOM要素への参照
import { useRef } from "react";

function FocusInput() {
  const inputRef = useRef<HTMLInputElement>(null);

  function handleClick() {
    inputRef.current?.focus(); // 実際のDOM要素にアクセスできる
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>入力欄にフォーカス</button>
    </>
  );
}

useRefは主に2つの用途で使います。1つはDOM要素に直接アクセスしたいとき(フォーカス制御など)、 もう1つは「再描画をさせたくないが値は保持しておきたい」とき(前回の値、タイマーIDなど)です。 .current を書き換えても画面は再描画されません。

12state共有・受け渡し(リフトアップ)

複数のコンポーネントで同じstateを共有したいときの基本パターンです。

それぞれのコンポーネントが独立してstateを持つと、互いに連動できません。 連動させたい場合は、stateを両者の共通の親に持たせ、値と更新関数をpropsとして子に渡します。 これを「state を持ち上げる(lifting state up)」と呼びます。

App.tsx
import { useState } from "react";

interface CounterButtonProps {
  count: number;
  onIncrement: () => void;
}

function CounterButton({ count, onIncrement }: CounterButtonProps) {
  return <button onClick={onIncrement}>合計: {count}</button>;
}

export default function App() {
  const [count, setCount] = useState<number>(0);
  const increment = () => setCount((prev) => prev + 1);

  return (
    <div>
      <CounterButton count={count} onIncrement={increment} />
      <CounterButton count={count} onIncrement={increment} />
    </div>
  );
}

2つのボタンは同じ count を参照しているので、どちらを押しても両方の表示が更新されます。 「更新したい関数(onIncrement)」もpropsとして渡している点に注目してください。

Contextについて(簡単に)

コンポーネントの階層が深くなると、途中の全コンポーネントにpropsをバケツリレーする必要が出てきます。 これを避けたい場合、ReactにはcreateContext / useContextという仕組みもあります (テーマやログインユーザー情報など、アプリ全体で使う値に向いています)。今回は基礎に絞るため詳細は割愛しますが、 propsのバケツリレーが辛くなってきたら調べてみる価値があります。

13カスタムフック

似たようなstateとロジックのまとまりを、自分で作った関数として再利用できます。

useで始まる自作の関数を作ることで、複数のコンポーネントで同じロジックを使い回せます。中身はただの関数で、 内部で useStateuseEffect などの既存のHooksを呼び出しているだけです。

useCounter.ts
import { useState } from "react";

function useCounter(initial: number = 0) {
  const [count, setCount] = useState<number>(initial);

  const increment = () => setCount((c) => c + 1);
  const decrement = () => setCount((c) => c - 1);
  const reset = () => setCount(initial);

  return { count, increment, decrement, reset };
}

// 使う側
function Counter() {
  const { count, increment, decrement, reset } = useCounter(0);
  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>リセット</button>
    </div>
  );
}
Hooksのルール

Hooks(useState, useEffect, 自作フックなど)は、 コンポーネントやカスタムフックのトップレベルでのみ呼び出せます。 if文やループ、ネストした関数の中で呼び出すことはできません。

14つまずきやすい点まとめ

最初の壁になりやすいポイントを一覧にしました。

症状原因・対処
stateを更新したのに画面が変わらない 更新関数(setXxx)を使わず直接代入している可能性。オブジェクト・配列は新しいものを作って渡す
ボタンを押した瞬間に関数が実行される onClick={fn()}のように括弧付きで呼び出している。onClick={fn}にする
リストの表示がおかしい・警告が出る keyが付いていない、またはインデックスをkeyにしていて並び替え時に崩れる
useEffectが無限ループする 依存配列に、エフェクト内で更新しているstateやレンダリングごとに新しく作られるオブジェクト・関数を入れている
入力しても文字が反映されない valueだけ設定してonChangeでstateを更新していない(制御コンポーネントの片手落ち)

15まとめ:簡単なTodoアプリ

ここまでの要素(state, props, イベント, リスト, フォーム)を組み合わせた例です。

App.tsx
import { useState } from "react";

interface Todo {
  id: number;
  text: string;
  done: boolean;
}

export default function App() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [input, setInput] = useState<string>("");

  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    if (input.trim() === "") return;

    const newTodo: Todo = { id: Date.now(), text: input, done: false };
    setTodos((prev) => [...prev, newTodo]); // 既存配列 + 新要素
    setInput("");
  }

  function toggleTodo(id: number) {
    setTodos((prev) =>
      prev.map((todo) =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    );
  }

  function deleteTodo(id: number) {
    setTodos((prev) => prev.filter((todo) => todo.id !== id));
  }

  return (
    <div>
      <h1>Todoリスト</h1>

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="やることを入力"
        />
        <button type="submit">追加</button>
      </form>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <span
              style={{ textDecoration: todo.done ? "line-through" : "none" }}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>削除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

このコード1つで、state(todos, input)、フォームの制御、 リストレンダリングとkey、イベントハンドラ、条件付きスタイル、配列の不変更新(map / filter / スプレッド構文)を一通り使っています。 ここまで理解できていれば、簡単なReactアプリは自力で組み立てられるはずです。

16実践チュートリアル:じゃんけんゲームを作ろう

ここまで学んだ知識を総動員して、実際に遊べるミニゲームを作ります。手を動かしながら覚えるのが一番の近道です。

🎮 じゃんけん VS コンピュータ

プレイヤーが✊✌️✋のボタンを押すと、コンピュータがランダムに手を出し、勝敗を自動判定してスコアを記録する—— そんなミニゲームを5つのステップで組み立てます。Todoアプリより一段階シンプルですが、 state・イベント処理・条件分岐・配列を「ゲームらしい手触り」で体験できるのがポイントです。

各ステップは前のステップのコードに数行足すだけです。写経しながら1ステップずつブラウザで確認してみてください。

STEP 1

土台となる画面を作る

まずはロジックなしで、タイトルと3つの手のボタンだけを並べます。ゲームの「見た目」から作るのがコツです。

App.tsx
export default function App() {
  return (
    <div>
      <h1>✊✌️✋ じゃんけんゲーム</h1>
      <div>
        <button>✊ グー</button>
        <button>✌️ チョキ</button>
        <button>✋ パー</button>
      </div>
    </div>
  );
}
✅ ここまでできたらOK: ブラウザに3つのボタンが表示されていれば成功です。まだ押しても何も起きません。
STEP 2

プレイヤーの手をstateに記録する

ボタンを押したら、その手を useState で覚えておきます。4章のprops・5章のstateの応用です。

App.tsx
import { useState } from "react";

type Hand = "グー" | "チョキ" | "パー";

export default function App() {
  const [playerHand, setPlayerHand] = useState<Hand | null>(null);

  return (
    <div>
      <h1>✊✌️✋ じゃんけんゲーム</h1>
      <div>
        <button onClick={() => setPlayerHand("グー")}>✊ グー</button>
        <button onClick={() => setPlayerHand("チョキ")}>✌️ チョキ</button>
        <button onClick={() => setPlayerHand("パー")}>✋ パー</button>
      </div>
      {playerHand && <p>あなたの手: {playerHand}</p>}
    </div>
  );
}
💡 なぜアロー関数で包むの?

onClick={() => setPlayerHand("グー")} のようにアロー関数で包んでいるのは、 "グー"という引数を渡したいからです(6章参照)。onClick={setPlayerHand("グー")} と書くと、クリック前に即実行されてしまうので注意してください。

✅ ここまでできたらOK: ボタンを押すと「あなたの手: グー」のように表示が切り替わります。
STEP 3

コンピュータの手をランダムに決める

配列からランダムに1つ選ぶ、ただのTypeScriptの関数を用意します。Reactに関係ない部分は普通のロジックでOKです。

App.tsx(抜粋・追加分)
const HANDS: Hand[] = ["グー", "チョキ", "パー"];

function getComputerHand(): Hand {
  const index = Math.floor(Math.random() * HANDS.length);
  return HANDS[index];
}

// App コンポーネントの中に追加
const [computerHand, setComputerHand] = useState<Hand | null>(null);

function handlePlay(hand: Hand) {
  setPlayerHand(hand);
  setComputerHand(getComputerHand());
}

// ボタン側は onClick={() => handlePlay("グー")} のように差し替える
✅ ここまでできたらOK: ボタンを押すたびに、コンピュータの手もランダムに変わって表示されます。
STEP 4

勝敗を判定して結果を表示する

2つの手を比べて勝敗を返す純粋な関数を作り、結果をJSXで条件分岐して表示します(7章の条件付きレンダリング)。

App.tsx(抜粋・追加分)
type Result = "WIN" | "LOSE" | "DRAW";

function judge(player: Hand, computer: Hand): Result {
  if (player === computer) return "DRAW";

  const winsAgainst: Record<Hand, Hand> = {
    グー: "チョキ",
    チョキ: "パー",
    パー: "グー",
  };

  return winsAgainst[player] === computer ? "WIN" : "LOSE";
}

// JSX側で結果を表示
{playerHand && computerHand && (
  <p>
    あなた: {playerHand} / コンピュータ: {computerHand} →{" "}
    {judge(playerHand, computerHand) === "WIN" && "🎉 あなたの勝ち!"}
    {judge(playerHand, computerHand) === "LOSE" && "😢 あなたの負け..."}
    {judge(playerHand, computerHand) === "DRAW" && "🤝 あいこ"}
  </p>
)}
✅ ここまでできたらOK: 勝ち・負け・あいこがボタンを押すたびに正しく表示されます。もうゲームとして遊べます!
STEP 5

スコアを記録する

最後に、勝敗数をstateで積み上げます。judgeの結果に応じて、直前の値をもとに更新するのがポイントです(5章の関数形の更新)。

App.tsx(完成版)
import { useState } from "react";

type Hand = "グー" | "チョキ" | "パー";
type Result = "WIN" | "LOSE" | "DRAW";

const HANDS: Hand[] = ["グー", "チョキ", "パー"];

function getComputerHand(): Hand {
  return HANDS[Math.floor(Math.random() * HANDS.length)];
}

function judge(player: Hand, computer: Hand): Result {
  if (player === computer) return "DRAW";
  const winsAgainst: Record<Hand, Hand> = { グー: "チョキ", チョキ: "パー", パー: "グー" };
  return winsAgainst[player] === computer ? "WIN" : "LOSE";
}

export default function App() {
  const [playerHand, setPlayerHand] = useState<Hand | null>(null);
  const [computerHand, setComputerHand] = useState<Hand | null>(null);
  const [score, setScore] = useState({ win: 0, lose: 0, draw: 0 });

  function handlePlay(hand: Hand) {
    const computer = getComputerHand();
    const result = judge(hand, computer);

    setPlayerHand(hand);
    setComputerHand(computer);
    setScore((prev) => ({
      win: prev.win + (result === "WIN" ? 1 : 0),
      lose: prev.lose + (result === "LOSE" ? 1 : 0),
      draw: prev.draw + (result === "DRAW" ? 1 : 0),
    }));
  }

  return (
    <div>
      <h1>✊✌️✋ じゃんけんゲーム</h1>
      <div>
        {HANDS.map((hand) => (
          <button key={hand} onClick={() => handlePlay(hand)}>{hand}</button>
        ))}
      </div>

      {playerHand && computerHand && (
        <p>あなた: {playerHand} / コンピュータ: {computerHand}</p>
      )}

      <p>戦績 — 🎉{score.win}勝 / 😢{score.lose}敗 / 🤝{score.draw}分</p>
    </div>
  );
}
🏁 完成: 何度でも遊べるじゃんけんゲームの完成です。勝敗が積み上がっていくのを確認してください。

🏆 腕試しチャレンジ

物足りない人は、次のような機能を自分で追加してみましょう。すべて、ここまで学んだ知識だけで実装できます。

  • 「3本先取したら終了」のルールを追加する(score.win === 3 で終了メッセージを出す)
  • 結果に応じてカードの背景色を変える(勝ち=緑、負け=赤、あいこ=グレーなど)
  • 「リセット」ボタンで score{ win: 0, lose: 0, draw: 0 } に戻す
  • 直近5回の対戦履歴を配列(state)に保存し、リストとして表示する(8章のリスト表示の応用)

17理解度チェックテスト

全10問、四択のミニテストです。答えを選んで「採点する」を押すと、その場で正解・解説・スコアが表示されます。

📝 迷ったら該当の章に戻って読み返してみましょう。何度でも挑戦できます。

Q1. Reactコンポーネントの名前として正しいのはどれ?

正解: b。JSXはタグの先頭が大文字か小文字かでReactコンポーネントとHTMLタグを区別するため、コンポーネント名は必ず大文字始まりにします(3章)。

Q2. 1つのコンポーネントから複数のJSX要素を返したいとき、正しい方法は?

正解: c。JSXは1つのルート要素しか返せないため、Fragment(<>...</>)や<div>などの親要素でまとめて包みます(3章)。

Q3. stateを更新する正しい書き方はどれ?(const [count, setCount] = useState(0)の場合)

正解: b。stateは直接代入せず、必ず更新関数(setCountなど、setから始まる関数)を呼んで更新します(5章)。

Q4. 直前の値をもとにstateを安全に更新したいとき、推奨される書き方は?

正解: b。関数形(prevを受け取る形)で更新すると、常に最新の値をもとに計算されるため、連続更新や非同期処理が絡む場面でも安全です(5章)。

Q5. ボタンをクリックしたときに関数を実行する正しい書き方は?

正解: c。括弧を付けて呼び出すとレンダリング時に即実行されてしまいます。関数そのもの(参照)を渡します(6章)。

Q6. リストをレンダリングするとき、keyに指定するべきものは?

正解: b。並び替えや削除があるとインデックスは崩れやすいため、データ由来の一意なIDを使うのが基本です(8章)。

Q7. count0のとき、{count && <p>件あります</p>}と書くと画面はどうなる?

正解: b。0はfalsyですがReactはそのまま描画してしまうため、{count > 0 && ...}のように必ず真偽値になる条件にします(7章)。

Q8. useEffect(fn, [])のように依存配列を空にすると、いつ実行される?

正解: b。空配列は「マウント時に1回だけ実行」を意味します。特定の値の変化時にも実行したい場合は、その値を配列に入れます(10章)。

Q9. フォームのinputにvalueonChangeを両方設定するのはなぜ?

正解: b。valueだけだと入力できず、onChangeだけだと表示に反映されません。両方揃えて初めてstateと表示が同期する「制御コンポーネント」になります(9章)。

Q10. Hooks(useStateなど)を呼び出してよい場所は?

正解: c。Hooksは条件分岐やループ、ネストした関数の中では呼び出せず、必ずコンポーネントの直下(トップレベル)で呼び出します(13章)。

18次のステップ

このガイドで扱わなかった、次に学ぶと良いトピックです。

  • useContext — propsのバケツリレーを避けてデータを共有する仕組み
  • useReducer — stateの更新ロジックが複雑になった場合の整理方法
  • useMemo / useCallback — 計算結果や関数をメモ化してパフォーマンスを最適化する仕組み
  • React Router — 画面遷移(ルーティング)をReact単体のアプリに追加するライブラリ
  • 状態管理ライブラリ(Zustand, Redux など)— アプリが大きくなったときの選択肢

このガイドはあえてNext.jsなどのフレームワークやサーバーサイドの話を除外しています。 React自体の基礎が固まったら、必要に応じてルーティングやサーバーサイドレンダリングを提供するフレームワークの学習に進むとスムーズです。