モダンJavaScriptの基礎から始める挫折しないためのReact入門 60日コース | Reactの基本 - DAY 20:React基礎総合演習

React 60日で身につけるReact
スポンサーリンク
スポンサーリンク
  1. DAY 20:React基礎総合演習 前半 ― じゃんけんアプリで基礎を一気に使ってみる
  2. じゃんけんアプリのゴールをイメージする
    1. どんな動きをするアプリかを言語化する
  3. コンポーネント設計 ― 役割ごとに分けて考える
    1. どんなコンポーネントに分けるか
  4. useStateで「手」と「結果」を管理する
    1. Stateとして持つべきものを決める
  5. Eventと条件分岐 ― 手を選んだときの処理を考える
    1. 手を選んだときに何をするかをステップで整理する
  6. Propsで「ボタン」と「結果表示」を分ける
    1. HandButtonコンポーネントを作る
    2. ResultViewコンポーネントを作る
  7. コンポーネントを組み合わせて、じゃんけんアプリを完成させる
    1. JankenAppにHandButtonとResultViewを組み込む
  8. DAY 20 前半のまとめ
  9. DAY 20:React基礎総合演習 中盤 ― じゃんけんアプリを「設計目線」で深掘りする
  10. コンポーネント分割をもう一度見直す
    1. 親コンポーネントと子コンポーネントの役割
  11. useStateの置き場所と「単一の情報源」
    1. Stateはどこに置くべきか
  12. Propsで「データ」と「イベント」を渡す
    1. HandButtonに渡すPropsを整理する
  13. Eventと条件分岐を「読みやすく」書く
    1. 勝敗判定ロジックを関数に切り出す
    2. イベントハンドラの中で「処理の流れ」を明確にする
  14. Stateの初期値と「未選択状態」を意識する
    1. ResultViewでの表示ロジック
  15. 中盤のまとめ用テンプレート
  16. DAY 20 中盤のまとめ
  17. DAY 20:React基礎総合演習 後半 ― じゃんけんアプリを「完成形」に育てる
  18. じゃんけんアプリに「勝ち数・負け数・あいこ数」を追加する
    1. 何をStateとして増やすかを決める
  19. 条件分岐+State更新で「勝ち数・負け数」を増やす
    1. 勝敗判定の結果に応じてカウンターを更新する
  20. ComponentとPropsで「スコア表示」を切り出す
    1. ScoreBoardコンポーネントを作る
  21. リセット機能を追加して「全体のState」をまとめて初期化する
    1. じゃんけんアプリ全体をリセットするボタン
  22. 完成版じゃんけんアプリテンプレート
  23. DAY 20:React基礎総合演習 後半のまとめ

DAY 20:React基礎総合演習 前半 ― じゃんけんアプリで基礎を一気に使ってみる

DAY 20の前半では、これまで学んできたReactの基礎をまとめて使うために、じゃんけんアプリを制作していきます。 このミニアプリの中で、次の要素をすべて使います。

  • Component(コンポーネント)
  • Props
  • useState
  • Event(イベント)
  • 条件分岐

「手を選ぶ」「勝敗を判定する」「結果を表示する」という流れを通して、Reactの基本的な考え方を体で理解していくことを目標にします。

じゃんけんアプリのゴールをイメージする

どんな動きをするアプリかを言語化する

まず、完成形のイメージをはっきりさせます。

  • ユーザーが「グー」「チョキ」「パー」のボタンから1つ選ぶ
  • コンピューター側の手がランダムに決まる
  • 「あなたの手」「コンピューターの手」「結果(勝ち・負け・あいこ)」を画面に表示する
  • もう一度ボタンを押せば、何度でも勝負できる

この動きの中に、コンポーネント・Props・useState・イベント・条件分岐が自然に含まれるように設計していきます。

コンポーネント設計 ― 役割ごとに分けて考える

どんなコンポーネントに分けるか

じゃんけんアプリを、次のようなコンポーネントに分けて考えます。

  • JankenApp
    • アプリ全体をまとめる親コンポーネント
    • State(手・結果)を持つ
  • HandButton
    • 「グー」「チョキ」「パー」のボタンを表すコンポーネント
    • Propsで「どの手か」「クリック時の処理」を受け取る
  • ResultView
    • 「あなたの手」「コンピューターの手」「結果」を表示するコンポーネント
    • Propsで表示する内容を受け取る

このように役割を分けることで、ComponentとPropsの関係を自然に使えるようになります。

useStateで「手」と「結果」を管理する

Stateとして持つべきものを決める

じゃんけんアプリでStateとして持つべきものは、次のようなものです。

  • あなたの手 → playerHand
  • コンピューターの手 → cpuHand
  • 結果(勝ち・負け・あいこ) → result

これらを、親コンポーネント JankenApp の中で useState を使って管理します。

import { useState } from "react";

function JankenApp() {
  const [playerHand, setPlayerHand] = useState(null); // "グー" など
  const [cpuHand, setCpuHand] = useState(null);       // "グー" など
  const [result, setResult] = useState("");           // "勝ち" "負け" "あいこ"

  return (
    <div>
      <h2>じゃんけんアプリ</h2>
      <p>React基礎総合演習</p>
    </div>
  );
}

export default JankenApp;
JSX

ここで、

  • playerHand → ユーザーが選んだ手
  • cpuHand → コンピューター側の手
  • result → 勝敗の文字列

という役割を持たせています。

重要ポイント:

  • 「画面に表示したい状態」は、必ずStateとして持つようにします。
  • じゃんけんでは、「手」と「結果」がUIの中心となるStateです。

Eventと条件分岐 ― 手を選んだときの処理を考える

手を選んだときに何をするかをステップで整理する

ユーザーが「グー」「チョキ」「パー」のボタンをクリックしたときに、次の処理を行います。

  1. ユーザーの手(playerHand)を更新する
  2. コンピューターの手(cpuHand)をランダムに決める
  3. 2つの手を比較して、結果(result)を条件分岐で決める

この一連の処理を、1つの関数 handlePlayerHandSelect にまとめます。

function JankenApp() {
  const [playerHand, setPlayerHand] = useState(null);
  const [cpuHand, setCpuHand] = useState(null);
  const [result, setResult] = useState("");

  const hands = ["グー", "チョキ", "パー"];

  const getRandomHand = () => {
    const index = Math.floor(Math.random() * hands.length);
    return hands[index];
  };

  const judgeResult = (player, cpu) => {
    if (player === cpu) {
      return "あいこ";
    }

    if (
      (player === "グー" && cpu === "チョキ") ||
      (player === "チョキ" && cpu === "パー") ||
      (player === "パー" && cpu === "グー")
    ) {
      return "勝ち";
    }

    return "負け";
  };

  const handlePlayerHandSelect = (selectedHand) => {
    const nextPlayerHand = selectedHand;
    const nextCpuHand = getRandomHand();
    const nextResult = judgeResult(nextPlayerHand, nextCpuHand);

    setPlayerHand(nextPlayerHand);
    setCpuHand(nextCpuHand);
    setResult(nextResult);
  };

  return (
    <div>
      <h2>じゃんけんアプリ</h2>
      <p>React基礎総合演習</p>
    </div>
  );
}
JSX

ここで起きていることをステップで整理すると、

  • getRandomHand
    • 配列 hands からランダムに1つ選びます。
  • judgeResult(player, cpu)
    • 条件分岐で「勝ち」「負け」「あいこ」を判定します。
  • handlePlayerHandSelect(selectedHand)
    • ユーザーの手を selectedHand として受け取り
    • コンピューターの手をランダムに決め
    • 勝敗を判定し
    • 3つのStateをまとめて更新します。

重要ポイント:

  • Event(クリック)をきっかけに、useState でStateを更新し、条件分岐で結果を決める流れが、Reactの基本パターンです。
  • 「処理の流れ」を関数の中にきれいに閉じ込めることで、コードが読みやすくなります。

Propsで「ボタン」と「結果表示」を分ける

HandButtonコンポーネントを作る

次に、「グー」「チョキ」「パー」のボタンをコンポーネントとして切り出します。 このコンポーネントは、Propsで次の情報を受け取ります。

  • label → ボタンに表示する文字(例:”グー”)
  • onClick → クリックされたときに呼び出す関数
function HandButton({ label, onClick }) {
  return (
    <button onClick={() => onClick(label)}>
      {label}
    </button>
  );
}
JSX

ここで、

  • label → Propsとして渡された文字列
  • onClick → Propsとして渡されたイベントハンドラ
  • onClick={() => onClick(label)} → ボタンがクリックされたときに、label を引数として渡して呼び出す

という役割になっています。

ResultViewコンポーネントを作る

次に、「あなたの手」「コンピューターの手」「結果」を表示するコンポーネントを作ります。 このコンポーネントは、Propsで次の情報を受け取ります。

  • playerHand
  • cpuHand
  • result
function ResultView({ playerHand, cpuHand, result }) {
  return (
    <div>
      <h3>結果</h3>
      <p>あなたの手:{playerHand ?? "未選択"}</p>
      <p>コンピューターの手:{cpuHand ?? "未選択"}</p>
      <p>勝敗:{result || "まだ勝負していません"}</p>
    </div>
  );
}
JSX

ここで、

  • playerHand ?? "未選択"playerHandnull のときは「未選択」と表示
  • result || "まだ勝負していません"result が空文字のときは「まだ勝負していません」と表示

というように、Stateの初期状態も考慮した表示をしています。

コンポーネントを組み合わせて、じゃんけんアプリを完成させる

JankenAppにHandButtonとResultViewを組み込む

最後に、JankenApp の中で HandButtonResultView を使って、じゃんけんアプリを完成させます。

import { useState } from "react";

function HandButton({ label, onClick }) {
  return (
    <button onClick={() => onClick(label)}>
      {label}
    </button>
  );
}

function ResultView({ playerHand, cpuHand, result }) {
  return (
    <div>
      <h3>結果</h3>
      <p>あなたの手:{playerHand ?? "未選択"}</p>
      <p>コンピューターの手:{cpuHand ?? "未選択"}</p>
      <p>勝敗:{result || "まだ勝負していません"}</p>
    </div>
  );
}

function JankenApp() {
  const [playerHand, setPlayerHand] = useState(null);
  const [cpuHand, setCpuHand] = useState(null);
  const [result, setResult] = useState("");

  const hands = ["グー", "チョキ", "パー"];

  const getRandomHand = () => {
    const index = Math.floor(Math.random() * hands.length);
    return hands[index];
  };

  const judgeResult = (player, cpu) => {
    if (player === cpu) {
      return "あいこ";
    }

    if (
      (player === "グー" && cpu === "チョキ") ||
      (player === "チョキ" && cpu === "パー") ||
      (player === "パー" && cpu === "グー")
    ) {
      return "勝ち";
    }

    return "負け";
  };

  const handlePlayerHandSelect = (selectedHand) => {
    const nextPlayerHand = selectedHand;
    const nextCpuHand = getRandomHand();
    const nextResult = judgeResult(nextPlayerHand, nextCpuHand);

    setPlayerHand(nextPlayerHand);
    setCpuHand(nextCpuHand);
    setResult(nextResult);
  };

  return (
    <div>
      <h2>じゃんけんアプリ</h2>
      <p>React基礎総合演習</p>

      <div>
        <h3>手を選んでください</h3>
        {hands.map((hand) => (
          <HandButton
            key={hand}
            label={hand}
            onClick={handlePlayerHandSelect}
          />
        ))}
      </div>

      <ResultView
        playerHand={playerHand}
        cpuHand={cpuHand}
        result={result}
      />
    </div>
  );
}

export default JankenApp;
JSX

この完成版の中には、

  • Component:JankenApp / HandButton / ResultView
  • Props:label / onClick / playerHand / cpuHand / result
  • useState:手と結果のState管理
  • Event:ボタンクリックで handlePlayerHandSelect を呼び出す
  • 条件分岐:judgeResult で勝敗を判定する

という、Reactの基礎要素がすべて詰まっています。

DAY 20 前半のまとめ

DAY 20の前半では、

  • じゃんけんアプリという具体的な題材を通して、Component・Props・useState・Event・条件分岐をまとめて使ったこと
  • 「手を選ぶ → コンピューターの手を決める → 勝敗を判定する → 結果を表示する」という一連の流れを、関数の中にきれいに閉じ込めたこと
  • 親コンポーネントでStateを管理し、子コンポーネントにPropsで渡すというReactの基本構造を体感したこと
  • Stateの初期値や未選択状態も考慮した表示(null や空文字への対応)を行い、実用的なUIに近づけたこと

をステップバイステップで確認しました。

このじゃんけんアプリをベースに、 「勝ち数・負け数をカウントする」「履歴を表示する」「アニメーションを付ける」など、 少しずつ機能を拡張していくことで、Reactの基礎がさらに深く定着していきます。


DAY 20:React基礎総合演習 中盤 ― じゃんけんアプリを「設計目線」で深掘りする

DAY 20の中盤では、前半で作ったじゃんけんアプリを少し発展させながら、 Component・Props・useState・Event・条件分岐を「設計目線」で整理していきます。

単に動くコードから一歩進んで、 「なぜこの分け方なのか」「どこにStateを置くべきなのか」「Propsは何を渡すべきなのか」 という視点を持つことで、Reactの基礎がぐっと実務に近づきます。

コンポーネント分割をもう一度見直す

親コンポーネントと子コンポーネントの役割

じゃんけんアプリでは、次のような役割分担が自然です。

  • 親コンポーネント:JankenApp
    • アプリ全体の状態(State)を管理する
    • ロジック(勝敗判定・ランダムな手の決定)を持つ
    • 子コンポーネントに必要な情報をPropsで渡す
  • 子コンポーネント:HandButton
    • 「グー」「チョキ」「パー」のボタンを表示する
    • クリックされたときに、親から渡された関数を呼び出す
  • 子コンポーネント:ResultView
    • 「あなたの手」「コンピューターの手」「結果」を表示する
    • 表示に必要な値をPropsで受け取る

この分け方のポイントは、 「Stateは親に集約し、子は“表示とイベントの入り口”に専念させる」ということです。

useStateの置き場所と「単一の情報源」

Stateはどこに置くべきか

じゃんけんアプリで使うStateは、次の3つでした。

  • playerHand(あなたの手)
  • cpuHand(コンピューターの手)
  • result(勝敗)

これらをすべて JankenApp に置く理由は、 「画面全体の状態を一箇所で管理するため」です。

もし、ResultView の中で独自にStateを持ってしまうと、

  • 親と子で「どの値が正しいのか」が分かりにくくなる
  • 表示とロジックがバラバラになり、修正が難しくなる

といった問題が出てきます。

Reactでは、 「その情報を一番よく知っているコンポーネントにStateを置く」 という考え方が大事です。

じゃんけんアプリの場合、

  • 手を決めるロジック
  • 勝敗を判定するロジック

を持っているのは JankenApp なので、 そこにStateを集約するのが自然です。

Propsで「データ」と「イベント」を渡す

HandButtonに渡すPropsを整理する

HandButton コンポーネントは、次のように定義していました。

function HandButton({ label, onClick }) {
  return (
    <button onClick={() => onClick(label)}>
      {label}
    </button>
  );
}
JSX

ここで渡しているPropsは2つです。

  • label → ボタンに表示する文字(”グー” / “チョキ” / “パー”)
  • onClick → ボタンがクリックされたときに呼び出す関数

この設計のポイントは、

  • 「表示に必要なデータ(label)」
  • 「ユーザー操作を親に伝えるためのイベントハンドラ(onClick)」

という2種類のPropsを分けて考えていることです。

親コンポーネント側では、次のように使っています。

{hands.map((hand) => (
  <HandButton
    key={hand}
    label={hand}
    onClick={handlePlayerHandSelect}
  />
))}
JSX

ここで、

  • label={hand}"グー" などの文字列を渡す
  • onClick={handlePlayerHandSelect} → 「手が選ばれたときの処理」を渡す

という形になっています。

重要ポイント:

  • Propsは、「子コンポーネントに必要な情報」と「子から親に伝えたいイベントの入り口」を渡すための仕組みです。
  • 子は「何を表示するか」と「いつ親の関数を呼ぶか」だけを知っていればよく、ロジックの中身は親が持ちます。

Eventと条件分岐を「読みやすく」書く

勝敗判定ロジックを関数に切り出す

勝敗判定のロジックは、次のように書いていました。

const judgeResult = (player, cpu) => {
  if (player === cpu) {
    return "あいこ";
  }

  if (
    (player === "グー" && cpu === "チョキ") ||
    (player === "チョキ" && cpu === "パー") ||
    (player === "パー" && cpu === "グー")
  ) {
    return "勝ち";
  }

  return "負け";
};
JSX

この関数の役割は、

  • 引数として「あなたの手」と「コンピューターの手」を受け取る
  • 条件分岐で勝敗を判定する
  • 結果の文字列(”勝ち” / “負け” / “あいこ”)を返す

というものです。

このようにロジックを関数に切り出すことで、

  • handlePlayerHandSelect の中身がスッキリする
  • 勝敗判定だけをテストしたり、修正したりしやすくなる

というメリットがあります。

イベントハンドラの中で「処理の流れ」を明確にする

handlePlayerHandSelect は、次のようになっていました。

const handlePlayerHandSelect = (selectedHand) => {
  const nextPlayerHand = selectedHand;
  const nextCpuHand = getRandomHand();
  const nextResult = judgeResult(nextPlayerHand, nextCpuHand);

  setPlayerHand(nextPlayerHand);
  setCpuHand(nextCpuHand);
  setResult(nextResult);
};
JSX

ここでのポイントは、

  1. 「次の値」を変数として明示していること
    • nextPlayerHand
    • nextCpuHand
    • nextResult
  2. その「次の値」をStateに反映していること
    • setPlayerHand(nextPlayerHand);
    • setCpuHand(nextCpuHand);
    • setResult(nextResult);

このように書くことで、

  • 「何が起きているか」が一行ずつ追いやすくなる
  • State更新の順番や意味が読み手に伝わりやすくなる

という効果があります。

重要ポイント:

  • Eventハンドラの中では、「処理の流れ」を変数名と関数名で分かりやすく表現することが大事です。
  • 条件分岐やランダム処理は、できるだけ別関数に切り出して、イベントハンドラをシンプルに保つと読みやすくなります。

Stateの初期値と「未選択状態」を意識する

ResultViewでの表示ロジック

ResultView コンポーネントでは、次のように表示していました。

function ResultView({ playerHand, cpuHand, result }) {
  return (
    <div>
      <h3>結果</h3>
      <p>あなたの手:{playerHand ?? "未選択"}</p>
      <p>コンピューターの手:{cpuHand ?? "未選択"}</p>
      <p>勝敗:{result || "まだ勝負していません"}</p>
    </div>
  );
}
JSX

ここで使っているポイントは、

  • playerHand ?? "未選択"
    • playerHandnull のときは「未選択」と表示する
  • result || "まだ勝負していません"
    • result が空文字のときは「まだ勝負していません」と表示する

という、「初期状態」を考慮した表示です。

Reactでは、 「Stateがまだ決まっていない状態」も必ず存在します。

  • 最初のレンダリングでは、ユーザーはまだ何も選んでいない
  • そのときに何を表示するかを決めておく必要がある

この視点を持つことで、 「実際に使えるUI」に近づいていきます。

中盤のまとめ用テンプレート

最後に、DAY 20中盤の内容を反映した、読みやすさを意識したじゃんけんアプリのテンプレートを示します。

import { useState } from "react";

function HandButton({ label, onClick }) {
  return (
    <button onClick={() => onClick(label)}>
      {label}
    </button>
  );
}

function ResultView({ playerHand, cpuHand, result }) {
  return (
    <div>
      <h3>結果</h3>
      <p>あなたの手:{playerHand ?? "未選択"}</p>
      <p>コンピューターの手:{cpuHand ?? "未選択"}</p>
      <p>勝敗:{result || "まだ勝負していません"}</p>
    </div>
  );
}

function JankenApp() {
  const [playerHand, setPlayerHand] = useState(null);
  const [cpuHand, setCpuHand] = useState(null);
  const [result, setResult] = useState("");

  const hands = ["グー", "チョキ", "パー"];

  const getRandomHand = () => {
    const index = Math.floor(Math.random() * hands.length);
    return hands[index];
  };

  const judgeResult = (player, cpu) => {
    if (player === cpu) {
      return "あいこ";
    }

    if (
      (player === "グー" && cpu === "チョキ") ||
      (player === "チョキ" && cpu === "パー") ||
      (player === "パー" && cpu === "グー")
    ) {
      return "勝ち";
    }

    return "負け";
  };

  const handlePlayerHandSelect = (selectedHand) => {
    const nextPlayerHand = selectedHand;
    const nextCpuHand = getRandomHand();
    const nextResult = judgeResult(nextPlayerHand, nextCpuHand);

    setPlayerHand(nextPlayerHand);
    setCpuHand(nextCpuHand);
    setResult(nextResult);
  };

  return (
    <div>
      <h2>じゃんけんアプリ</h2>
      <p>React基礎総合演習</p>

      <div>
        <h3>手を選んでください</h3>
        {hands.map((hand) => (
          <HandButton
            key={hand}
            label={hand}
            onClick={handlePlayerHandSelect}
          />
        ))}
      </div>

      <ResultView
        playerHand={playerHand}
        cpuHand={cpuHand}
        result={result}
      />
    </div>
  );
}

export default JankenApp;
JSX

DAY 20 中盤のまとめ

DAY 20の中盤では、

  • 親コンポーネントにStateとロジックを集約し、子コンポーネントにはPropsで「データ」と「イベント」を渡す設計を確認したこと
  • useState の置き場所を「その情報を一番よく知っているコンポーネント」にする、という考え方を身につけたこと
  • Eventと条件分岐を関数に切り出し、「処理の流れ」を読みやすく書く練習をしたこと
  • 初期状態や未選択状態も含めてUIを設計し、実際に使えるじゃんけんアプリに近づけたこと

が大きなポイントになります。

後半では、このじゃんけんアプリに「勝ち数・負け数のカウンター」などを追加し、 State更新とコンポーネント設計をさらに一段深く練習していきます。


DAY 20:React基礎総合演習 後半 ― じゃんけんアプリを「完成形」に育てる

DAY 20の後半では、前半・中盤で作ってきたじゃんけんアプリを、 「基礎の総まとめ」として、少し“完成形”に近づけることを目標にします。

使うものは変わりません。

  • Component
  • Props
  • useState
  • Event
  • 条件分岐

ですが、ここではそれぞれを「どう組み合わせると、読みやすくて拡張しやすいアプリになるか」という視点で深掘りしていきます。

じゃんけんアプリに「勝ち数・負け数・あいこ数」を追加する

何をStateとして増やすかを決める

まず、じゃんけんアプリを少しだけゲームらしくするために、

  • 勝ち数 → winCount
  • 負け数 → loseCount
  • あいこ数 → drawCount

を追加してみます。

これらは、「勝負の履歴」を表す重要な情報なので、JankenApp のStateとして持つのが自然です。

import { useState } from "react";

function JankenApp() {
  const [playerHand, setPlayerHand] = useState(null);
  const [cpuHand, setCpuHand] = useState(null);
  const [result, setResult] = useState("");

  const [winCount, setWinCount] = useState(0);
  const [loseCount, setLoseCount] = useState(0);
  const [drawCount, setDrawCount] = useState(0);

  // ...(後でロジックを追加します)

  return (
    <div>
      <h2>じゃんけんアプリ</h2>
      <p>React基礎総合演習</p>
    </div>
  );
}

export default JankenApp;
JSX

重要ポイント:

  • 「画面全体の状態」を表すものは、親コンポーネントに集約します。
  • 勝ち数・負け数・あいこ数は、じゃんけんアプリ全体の状態なので、JankenApp に置くのが自然です。

条件分岐+State更新で「勝ち数・負け数」を増やす

勝敗判定の結果に応じてカウンターを更新する

すでにある judgeResult 関数を活かしつつ、 勝敗に応じてカウンターを更新するロジックを追加します。

const judgeResult = (player, cpu) => {
  if (player === cpu) {
    return "あいこ";
  }

  if (
    (player === "グー" && cpu === "チョキ") ||
    (player === "チョキ" && cpu === "パー") ||
    (player === "パー" && cpu === "グー")
  ) {
    return "勝ち";
  }

  return "負け";
};
JSX

この結果を使って、handlePlayerHandSelect の中でカウンターを更新します。

const handlePlayerHandSelect = (selectedHand) => {
  const nextPlayerHand = selectedHand;
  const nextCpuHand = getRandomHand();
  const nextResult = judgeResult(nextPlayerHand, nextCpuHand);

  setPlayerHand(nextPlayerHand);
  setCpuHand(nextCpuHand);
  setResult(nextResult);

  if (nextResult === "勝ち") {
    setWinCount((prev) => prev + 1);
  } else if (nextResult === "負け") {
    setLoseCount((prev) => prev + 1);
  } else if (nextResult === "あいこ") {
    setDrawCount((prev) => prev + 1);
  }
};
JSX

ここで起きていることをステップで整理すると、

  1. ユーザーが手を選ぶ → handlePlayerHandSelect が呼ばれる
  2. nextPlayerHandnextCpuHand を決める
  3. judgeResult で勝敗を判定し、nextResult を得る
  4. 手と結果のStateを更新する
  5. nextResult に応じて、勝ち数・負け数・あいこ数のカウンターを更新する

重要ポイント:

  • 「前のStateをもとに1増やす」場面では、setWinCount((prev) => prev + 1) のような関数型更新を使います。
  • 条件分岐とState更新を組み合わせることで、「ロジックに意味を持たせたUI」が作れます。

ComponentとPropsで「スコア表示」を切り出す

ScoreBoardコンポーネントを作る

勝ち数・負け数・あいこ数を表示する部分を、 ScoreBoard というコンポーネントに切り出してみます。

function ScoreBoard({ winCount, loseCount, drawCount }) {
  return (
    <div>
      <h3>スコア</h3>
      <p>勝ち:{winCount}</p>
      <p>負け:{loseCount}</p>
      <p>あいこ:{drawCount}</p>
    </div>
  );
}
JSX

ここで、

  • winCount / loseCount / drawCount は、親コンポーネントからPropsとして渡される値です。
  • ScoreBoard は「表示専用」のコンポーネントであり、Stateは持ちません。

親コンポーネント側では、次のように使います。

<ScoreBoard
  winCount={winCount}
  loseCount={loseCount}
  drawCount={drawCount}
/>
JSX

重要ポイント:

  • 「表示だけを担当するコンポーネント」は、Propsでデータを受け取り、Stateを持たない形にするとシンプルになります。
  • 親がStateを持ち、子が表示を担当する構造は、Reactの基本パターンです。

リセット機能を追加して「全体のState」をまとめて初期化する

じゃんけんアプリ全体をリセットするボタン

次に、「リセット」ボタンを追加して、 手・結果・スコアをすべて初期状態に戻す機能を作ります。

const handleResetGame = () => {
  setPlayerHand(null);
  setCpuHand(null);
  setResult("");

  setWinCount(0);
  setLoseCount(0);
  setDrawCount(0);
};
JSX

この関数を、ボタンのイベントハンドラとして使います。

<button onClick={handleResetGame}>リセット</button>
JSX

ここで起きていることをステップで整理すると、

  1. ユーザーが「リセット」ボタンをクリックする
  2. handleResetGame が呼ばれる
  3. すべてのStateを初期値に戻す
    • 手 → null
    • 結果 → 空文字
    • スコア → 0

重要ポイント:

  • 「前のStateは関係なく、特定の値に戻したい」場面では、setX(初期値) のように直接値を渡します。
  • リセット機能は、「アプリ全体のStateをどう設計しているか」を確認する良い練習になります。

完成版じゃんけんアプリテンプレート

ここまでの内容をすべてまとめた、 DAY 20後半の「完成版じゃんけんアプリ」のテンプレートを示します。

import { useState } from "react";

function HandButton({ label, onClick }) {
  return (
    <button onClick={() => onClick(label)}>
      {label}
    </button>
  );
}

function ResultView({ playerHand, cpuHand, result }) {
  return (
    <div>
      <h3>結果</h3>
      <p>あなたの手:{playerHand ?? "未選択"}</p>
      <p>コンピューターの手:{cpuHand ?? "未選択"}</p>
      <p>勝敗:{result || "まだ勝負していません"}</p>
    </div>
  );
}

function ScoreBoard({ winCount, loseCount, drawCount }) {
  return (
    <div>
      <h3>スコア</h3>
      <p>勝ち:{winCount}</p>
      <p>負け:{loseCount}</p>
      <p>あいこ:{drawCount}</p>
    </div>
  );
}

function JankenApp() {
  const [playerHand, setPlayerHand] = useState(null);
  const [cpuHand, setCpuHand] = useState(null);
  const [result, setResult] = useState("");

  const [winCount, setWinCount] = useState(0);
  const [loseCount, setLoseCount] = useState(0);
  const [drawCount, setDrawCount] = useState(0);

  const hands = ["グー", "チョキ", "パー"];

  const getRandomHand = () => {
    const index = Math.floor(Math.random() * hands.length);
    return hands[index];
  };

  const judgeResult = (player, cpu) => {
    if (player === cpu) {
      return "あいこ";
    }

    if (
      (player === "グー" && cpu === "チョキ") ||
      (player === "チョキ" && cpu === "パー") ||
      (player === "パー" && cpu === "グー")
    ) {
      return "勝ち";
    }

    return "負け";
  };

  const handlePlayerHandSelect = (selectedHand) => {
    const nextPlayerHand = selectedHand;
    const nextCpuHand = getRandomHand();
    const nextResult = judgeResult(nextPlayerHand, nextCpuHand);

    setPlayerHand(nextPlayerHand);
    setCpuHand(nextCpuHand);
    setResult(nextResult);

    if (nextResult === "勝ち") {
      setWinCount((prev) => prev + 1);
    } else if (nextResult === "負け") {
      setLoseCount((prev) => prev + 1);
    } else if (nextResult === "あいこ") {
      setDrawCount((prev) => prev + 1);
    }
  };

  const handleResetGame = () => {
    setPlayerHand(null);
    setCpuHand(null);
    setResult("");

    setWinCount(0);
    setLoseCount(0);
    setDrawCount(0);
  };

  return (
    <div>
      <h2>じゃんけんアプリ</h2>
      <p>React基礎総合演習(DAY 20)</p>

      <div>
        <h3>手を選んでください</h3>
        {hands.map((hand) => (
          <HandButton
            key={hand}
            label={hand}
            onClick={handlePlayerHandSelect}
          />
        ))}
      </div>

      <ResultView
        playerHand={playerHand}
        cpuHand={cpuHand}
        result={result}
      />

      <ScoreBoard
        winCount={winCount}
        loseCount={loseCount}
        drawCount={drawCount}
      />

      <button onClick={handleResetGame}>リセット</button>
    </div>
  );
}

export default JankenApp;
JSX

DAY 20:React基礎総合演習 後半のまとめ

DAY 20の後半では、

  • じゃんけんアプリに「勝ち数・負け数・あいこ数」のカウンターを追加し、State更新の応用を体験したこと
  • 条件分岐とState更新を組み合わせて、「結果に応じてカウンターを増やす」ロジックを書いたこと
  • ScoreBoard コンポーネントを作り、Propsでスコアを渡すことで、表示専用コンポーネントの設計を確認したこと
  • リセット機能を通して、「アプリ全体のStateをどう初期化するか」を意識できるようになったこと
  • Component・Props・useState・Event・条件分岐を、1つのアプリの中で自然に組み合わせる感覚をつかんだこと

が大きなポイントになります。

読者のみなさんが、このじゃんけんアプリをさらに改造して、

  • 勝率を表示する
  • 何戦目かを表示する
  • 手をアイコンや画像にする

など、少しずつ遊び心を足していくことで、Reactの基礎が「自分のもの」として定着していきます。

タイトルとURLをコピーしました