PC-LABOを試すためのJavaScript・React入門(第3回)~JSX・コンポーネント・props・useState~【GitHub対応】

JavaScript / React

はじめに

第1回ではJavaScriptの基本文法を、第2回では配列・オブジェクト・map/filter・スプレッド構文など、Reactのコードに頻出する書き方を扱った。

入門シリーズの最終回となる今回は、いよいよReactそのものを扱う。JSX、コンポーネント、props、useStateの4つを、小さなコードで体験する。ここまで押さえておけば、「Reactで学ぶ 予定管理アプリ開発」の本編に、無理なく入れるはずである。

今回やること

  • Reactの基本的な考え方を知る
  • JSXで、画面の見た目を書く
  • コンポーネントとpropsで、画面を部品に分ける
  • mapとkeyで、一覧を表示する
  • useStateで、値が変わると画面も変わる仕組みを作る

Reactとは

Reactは、画面(UI)を作るためのJavaScriptのライブラリである。特徴は次の2つである。

  • 画面をコンポーネントという部品に分けて、組み合わせて作る
  • 値(state)が変わると、その値を使っている部分の画面を、Reactが自動で書き換える

「画面を直接書き換える」のではなく、「値を変えれば、画面は勝手に変わる」というのが、Reactの考え方である。

実行環境

今回は、第1・2回と違い、Node.jsで直接実行するのではなく、Vite(ヴィート)という開発ツールを使ってブラウザで動かす。

なぜViteを使うのか

Viteは、Node.jsの上で動くツールである。今回のReactのコードは、Node.jsだけでは動かしにくい、次のような事情がある。

  • 結果がブラウザの画面に表示される。第1・2回のconsole.logのようにターミナルに出力するのではなく、ブラウザで動かす必要がある。
  • ブラウザはJSXを理解できない。普通のJavaScriptに変換する必要がある。
  • Reactなどのライブラリ(npm installで入れたもの)を、ブラウザで使える形にまとめる必要がある。
  • ファイルを保存すると、ブラウザの表示が自動で更新されると便利である。

Viteは、この変換・まとめ・自動更新を、まとめて引き受けてくれる。Node.jsは、npmやViteそのものを動かすために、引き続き使っている。

ソースコードは、次の筆者のリポジトリの03_react-basicsフォルダにある。

GitHub – HappyTalk10/pc-labo-js-react-intro
Contribute to HappyTalk10/pc-labo-js-react-intro development by creating an account on GitHub.

Codespacesのターミナルで、次のコマンドを実行する。

cd 03_react-basics
npm install
npm run dev
  • npm install:必要なライブラリ(React、Vite)をダウンロードする。最初の1回だけでよい
  • npm run dev:Viteを起動して、開発用のサーバーとして動かす

npm run devのdevは、package.jsonのscriptsに書かれている。

"scripts": {
  "dev": "vite"
}

つまりnpm run devは、「devに書かれたviteコマンドを実行する」という意味である。viteコマンドを短い名前で呼び出せるようにしている、と考えればよい。

起動すると、ターミナルにViteの起動を知らせる表示と、http://localhost:5173/というURLが表示される。Codespacesでは、ポートが自動で転送されるので、表示される「ブラウザーで開く」のボタンから画面を開ける。

Viteは、このコマンドを実行したターミナルの中で動き続けている。ファイルをブラウザに渡す役割がViteで、受け取ったReactのコードを実行して画面を表示するのがブラウザである。止める時は、ターミナルでCtrl + Cを押す。ただし、止めると、画面を開き直せなくなる。

プロジェクトの構成

03_react-basics/
├── index.html
├── package.json
├── vite.config.js
└── src/
├── main.jsx
├── App.jsx
└── components/
├── Hello.jsx
├── PlanItem.jsx
├── PlanList.jsx
├── Counter.jsx
└── PlanAdder.jsx

ファイルの拡張子には、.jsと.jsxの2種類がある。この違いは、後ほど「JSX」の節で説明する。

画面が始まる場所:main.jsxとApp.jsx

最初に実行されるJavaScriptのファイルは、src/main.jsxである。index.htmlの中の、次の<script>タグで読み込まれている。

<script type="module" src="/src/main.jsx"></script>

src/main.jsxの中身は、次のとおりである。

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>
);

index.htmlにある<div id="root">の中に、Appというコンポーネントを描画している。ここは、「ここから画面が始まる」入口だと考えればよい。StrictModeは、開発中に問題を見つけやすくするための仕組みなので、今は気にしなくてよい。

src/App.jsxは、今回作る各コンポーネントを組み合わせて、1つの画面に仕上げるファイルである。Reactでは、このように小さな部品を組み合わせて、画面全体を作っていく。main.jsxが描画していたAppは、その画面全体にあたるコンポーネントである。

import Hello from "./components/Hello.jsx";
import PlanList from "./components/PlanList.jsx";
import Counter from "./components/Counter.jsx";
import PlanAdder from "./components/PlanAdder.jsx";

export default function App() {
  return (
    <div>
      <h1>React入門</h1>

      <h2>1. コンポーネントとprops</h2>
      <Hello name="PC-LABO" />
      <Hello name="React" />

      <h2>2. 一覧表示(map と key)</h2>
      <PlanList />

      <h2>3. useState:カウンター</h2>
      <Counter />

      <h2>4. useState:予定の追加・削除・完了</h2>
      <PlanAdder />
    </div>
  );
}

第2回で扱ったimportとexport defaultが、そのまま使われている。

JSX

App.jsxのreturnの中には、HTMLによく似た記述がある。これがJSXである。JavaScriptの中に、画面の見た目をそのまま書ける。
ブラウザはJSXを解釈できないので、Viteが自動でJavaScriptに変換してくれる。

JSXの中では、波括弧{}で囲むと、JavaScriptの式を埋め込める。

<p>こんにちは、{name}さん</p>

HTMLと違う点は、主に次の3つである。

  • 全体を1つの要素で囲む必要がある(Appでは<div>で囲んでいる)
  • <input>のように中身のない要素も、<input />と閉じる
  • HTMLのclassは、classNameと書く

【ファイルの拡張子】.jsと.jsx

JSXを書いたファイルは、拡張子を.jsxにする。
さて、このプロジェクトには、拡張子が.jsのファイルと.jsxのファイルがある。

  • .js:普通のJavaScriptだけが書かれたファイル(vite.config.js)
  • .jsx:JSXを含むファイル(main.jsx、App.jsx、componentsの中の各ファイル)

vite.config.jsは設定を書くだけで、画面の見た目は書かないので.jsである。一方、コンポーネントはreturnの中にJSXを書くので.jsxにする。

JSXを含むファイルを.jsのままにすると、Viteがエラーにする。importする時も、./App.jsxのように、拡張子まで記述している。

コンポーネントとprops

コンポーネントは、画面の部品となる関数である。名前は大文字で始める。JSXをreturnする関数が、そのままコンポーネントになる。

src/components/Hello.jsxである。

export default function Hello({ name }) {
  return <p>こんにちは、{name}さん</p>;
}

使う側では、HTMLの属性のように値を渡す。

<Hello name="PC-LABO" />
<Hello name="React" />

この渡す値をpropsと呼ぶ。コンポーネントの引数として、オブジェクトの形で受け取る。{ name }は、第2回で扱った分割代入である。

表示は次のようになる。同じ部品を、違う値で使い回せるのがポイントである。

こんにちは、PC-LABOさん
こんにちは、Reactさん

一覧表示:mapとkey

予定の一覧は、1件分を表示するコンポーネントと、一覧全体のコンポーネントに分けて作る。

まず、1件分のPlanItem.jsxである。

export default function PlanItem({ date, title, done }) {
  return (
    <li>
      {date} {title} {done ? "(完了)" : ""}
    </li>
  );
}

{done ? "(完了)" : ""}は、第2回で扱った三項演算子である。doneがtrueの時だけ「(完了)」と表示する。

次に、一覧全体のPlanList.jsxである。

import PlanItem from "./PlanItem.jsx";

const plans = [
  { id: 1, date: "2026-10-01", title: "歯医者", done: false },
  { id: 2, date: "2026-10-02", title: "会議", done: true },
  { id: 3, date: "2026-10-03", title: "飲み会", done: false },
];

export default function PlanList() {
  return (
    <ul>
      {plans.map((plan) => (
        <PlanItem
          key={plan.id}
          date={plan.date}
          title={plan.title}
          done={plan.done}
        />
      ))}
    </ul>
  );
}

第2回で学んだmapが、ここで活きる。「予定のオブジェクト」を、PlanItemというコンポーネントに変換して並べている。

keyは、Reactが「どの要素がどれか」を見分けるための目印である。mapで並べる要素には、必ずkeyをつける。他の要素と重ならない値を指定する必要があり、ここではidを使っている。

useState

ここからが、Reactの中心となる機能である。

画面に表示する値が変わった時に、画面も一緒に変わるようにしたい。そのために使うのがuseStateである。Counter.jsxで確認しよう。

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>カウント:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}
const [count, setCount] = useState(0);

この1行が、Reactの状態管理の基本形である。

  • useState(0):初期値を0として、stateを作る
  • count:現在の値
  • setCount:値を変更するための関数

useStateが返す配列を、第2回で扱った配列の分割代入で受け取っている。

ボタンのonClickには、クリックされた時に実行する関数を渡す。ここではsetCount(count + 1)で、値を1増やしている。

ポイントは、countを直接書き換えるのではなく、必ずsetCountを使うことである。setCountで値を変えると、Reactが画面を書き換えてくれる。count = count + 1のように変数を書き換えても、画面は変わらない。

ボタンを押すと、表示がカウント:0、カウント:1、カウント:2……と変わっていくことを確認しよう。

予定のリストをstateで管理する

最後に、第2回で学んだ配列の扱い方が、Reactでどう使われるかを確認する。PlanAdder.jsxは、予定の追加・削除・完了の切り替えができるコンポーネントである。

import { useState } from "react";

export default function PlanAdder() {
  const [plans, setPlans] = useState([
    { id: 1, title: "歯医者", done: false },
    { id: 2, title: "会議", done: false },
  ]);
  const [text, setText] = useState("");

  // 追加:スプレッド構文で、新しい配列を作る
  const addPlan = () => {
    if (text === "") return;
    setPlans([...plans, { id: Date.now(), title: text, done: false }]);
    setText("");
  };

  // 削除:filter で、指定した id 以外を残す
  const deletePlan = (id) => {
    setPlans(plans.filter((plan) => plan.id !== id));
  };

  // 完了の切り替え:map と スプレッド構文で、1件だけ更新する
  const toggleDone = (id) => {
    setPlans(
      plans.map((plan) =>
        plan.id === id
          ? { ...plan, done: !plan.done }
          : plan
      )
    );
  };

  return (
    <div>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="予定を入力"
      />
      <button onClick={addPlan}>追加</button>

      <ul>
        {plans.map((plan) => (
          <li key={plan.id}>
            {plan.title} {plan.done ? "(完了)" : ""}
            <button onClick={() => toggleDone(plan.id)}>完了/戻す</button>
            <button onClick={() => deletePlan(plan.id)}>削除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

stateは2つある。plansは予定の配列、textは入力欄に打ち込んでいる文字列である。

入力欄

<input
  value={text}
  onChange={(e) => setText(e.target.value)}
/>

入力欄の値も、stateで持つ。文字を打つたびにonChangeが実行され、e.target.value(入力された文字列)をsetTextでstateに入れる。入力欄の表示は、常にtextの値と一致する。

追加・削除・完了の切り替え

3つの操作は、第2回の「Reactの状態更新でよく使う形」と同じである。

  • 追加:[...plans, 新しい予定]
  • 削除:plans.filter((plan) => plan.id !== id)
  • 更新:plans.map(...)と{ ...plan, done: !plan.done }

どれも、元の配列を書き換えず、新しい配列を作ってsetPlansに渡している。第2回で「pushを使わない」と説明したのは、このためである。Reactは、新しい値に変わったことを手がかりに、画面を書き換える。元の配列を書き換えてしまうと、Reactは「変わった」と気づけず、画面が更新されない。

idには、ここでは簡易的にDate.now()(現在時刻のミリ秒)を使っている。

試してみよう

  • 入力欄に文字を入れて、「追加」を押す
  • 「完了/戻す」を押して、「(完了)」の表示が切り替わることを確認する
  • 「削除」を押して、予定が消えることを確認する

なお、このコンポーネントはページを再読み込みすると、予定が元に戻る。保存して残す方法(localStorage)は、本編で扱う。

今回のまとめ

  • Reactは、画面をコンポーネントに分けて作り、値が変わると画面も自動で変わる
  • JSXは、JavaScriptの中に画面の見た目を書く記法で、{}で式を埋め込める
  • コンポーネントは、大文字で始まる関数で、propsで値を受け取る
  • mapで一覧を表示する時は、keyをつける
  • useStateで値を持ち、変更には必ずset関数を使う
  • 配列のstateは、スプレッド構文・filter・mapで、新しい配列を作って更新する

入門シリーズのまとめと次のステップ

全3回の入門は、今回で完結である。

  • 第1回:変数・型・条件分岐・ループ・関数
  • 第2回:配列・オブジェクト・map/filter・import/export・JSON・Date
  • 第3回:JSX・コンポーネント・props・useState

ここまでで、「Reactで学ぶ 予定管理アプリ開発」に必要な土台は一通りそろった。次は、本編の第1回(環境構築)から、予定管理アプリを実際に作っていこう。今回出てきたコードが、本編でそのまま登場する。

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