Reactの基礎から:21の例でWebサイトを作る

React入門:21の例でWebサイトを作る

Reactは対話的なWebページを作るためのJavaScriptライブラリです。ページを「コンポーネント」と呼ばれる小さな部品に分けます。各コンポーネントはデータを受け取り、ページの一部を返します。この記事では、テキストを一つ表示するところから、メニュー、データから作るカード、フッターを備えた小さなWebサイトを組み立てるまでを扱います。すべての例は実際に実行したファイルで、示す出力も実際の出力です。

必要なもの

Node.js 18以降、JavaScriptの基礎、任意のエディタが必要です。npm install [email protected] [email protected] でパッケージを入れ、この記事のテストランナーで各例を実行します。steps を持つ例は、jsdomで作ったブラウザ上でボタンのクリックを再現します。

図解

Reactの仕組み:データと状態がコンポーネントに入り、HTMLが生成される
Reactの仕組み:データと状態がコンポーネントに入り、HTMLが生成される

例 1: 最初のテキストを表示する

コンポーネントはJSXを返す関数です。この例では見出しを作り、renderToStaticMarkupでHTMLに変換します。

import React from "react";

export function App() {
  return <h1>สวัสดี React</h1>;
}

**実際の出力**

<h1>สวัสดี React</h1>

例 2: mapでリストを表示する

mapで配列を項目ごとのliに変換します。データが変わるとHTMLを書き換えなくても画面が追従します。

import React from "react";

const fruits = ["ทุเรียน", "มังคุด", "มะม่วง"];

export function App() {
  return (
    <ul>
      {fruits.map(f => <li key={f}>{f}</li>)}
    </ul>
  );
}

**実際の出力**

<ul><li>ทุเรียน</li><li>มังคุด</li><li>มะม่วง</li></ul>

例 3: コンポーネントにpropsを渡す

propsは親から子へ渡すデータです。同じコンポーネントを異なるデータで再利用できます。

import React from "react";

function Greeting({ name, age }) {
  return <p>สวัสดี {name} อายุ {age} ปี</p>;
}

export function App() {
  return (
    <div>
      <Greeting name="สมชาย" age={20} />
      <Greeting name="สมหญิง" age={22} />
    </div>
  );
}

**実際の出力**

<div><p>สวัสดี สมชาย อายุ 20 ปี</p><p>สวัสดี สมหญิง อายุ 22 ปี</p></div>

例 4: 条件によって表示を変える

&& と三項演算子で、画面に何を表示するかを選びます。

import React from "react";

function Status({ online }) {
  return (
    <div>
      {online ? <span>ออนไลน์</span> : <span>ออฟไลน์</span>}
      {online && <em>พร้อมแชท</em>}
    </div>
  );
}

export function App() {
  return <Status online={true} />;
}

**実際の出力**

<div><span>ออนไลน์</span><em>พร้อมแชท</em></div>

例 5: Fragment

余分なdivが不要なときは、<> ... </> で複数の要素をまとめます。

import React from "react";

function Header() {
  return (
    <>
      <h2>หัวข้อ</h2>
      <p>ไม่ต้องครอบด้วย div</p>
    </>
  );
}

export function App() {
  return <Header />;
}

**実際の出力**

<h2>หัวข้อ</h2><p>ไม่ต้องครอบด้วย div</p>

例 6: テーブルのkey

データからテーブルを作り、idをkeyに使ってReactが各行を識別できるようにします。

import React from "react";

const students = [
  { id: 1, name: "สมชาย", score: 85 },
  { id: 2, name: "สมหญิง", score: 72 },
];

export function App() {
  return (
    <table>
      <tbody>
        {students.map(s => (
          <tr key={s.id}>
            <td>{s.name}</td>
            <td>{s.score}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

**実際の出力**

<table><tbody><tr><td>สมชาย</td><td>85</td></tr><tr><td>สมหญิง</td><td>72</td></tr></tbody></table>

例 7: styleはオブジェクトで書く

Reactのstyleはcamel caseのオブジェクトを受け取ります。例:backgroundColor、borderRadius。

import React from "react";

const box = {
  padding: 12,
  backgroundColor: "#eef4ff",
  borderRadius: 8,
  color: "#1a1a1a",
};

export function App() {
  return <div style={box}>กล่องที่ตกแต่งด้วย style object</div>;
}

**実際の出力**

<div style="padding:12px;background-color:#eef4ff;border-radius:8px;color:#1a1a1a">กล่องที่ตกแต่งด้วย style object</div>

例 8: children

開始タグと終了タグの間にJSXを置くと、コンポーネントは任意の内容を包めます。

import React from "react";

function Card({ title, children }) {
  return (
    <section>
      <h3>{title}</h3>
      <div>{children}</div>
    </section>
  );
}

export function App() {
  return (
    <Card title="ข่าวสาร">
      <p>เนื้อหาที่ส่งเข้าไปใน children</p>
    </Card>
  );
}

**実際の出力**

<section><h3>ข่าวสาร</h3><div><p>เนื้อหาที่ส่งเข้าไปใน children</p></div></section>

例 9: stateによるカウンター

useStateは変化する値を保持します。setterを呼ぶとReactが再描画します。この例ではボタンを2回押して変化を確認します。

import React, { useState } from "react";

export function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p data-out>นับ: {count}</p>
      <button id="inc" onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

export const steps = ["#inc", "#inc"];

**実際の出力**

ข้อความเริ่มต้น: นับ: 0
หลังคลิกครั้งที่ 1: นับ: 1
หลังคลิกครั้งที่ 2: นับ: 2
状態のサイクル:クリックでsetterが呼ばれ、Reactが再描画する
状態のサイクル:クリックでsetterが呼ばれ、Reactが再描画する

例 10: 制御されたinput

inputの値はstateで制御され、入力のたびにonChangeで更新されます。この例では初期値の0文字を表示します。

import React, { useState } from "react";

export function App() {
  const [text, setText] = useState("");
  return (
    <div>
      <input id="box" value={text} onChange={e => setText(e.target.value)} />
      <p>พิมพ์แล้ว: {text.length} ตัวอักษร</p>
    </div>
  );
}

**実際の出力**

<div><input id="box" value=""/><p>พิมพ์แล้ว: 0 ตัวอักษร</p></div>

例 11: オン・オフのスイッチ

booleanをstateに保存し、クリックのたびに !on で反転させます。

import React, { useState } from "react";

export function App() {
  const [on, setOn] = useState(false);
  return (
    <div>
      <p data-out>{on ? "เปิดอยู่" : "ปิดอยู่"}</p>
      <button id="toggle" onClick={() => setOn(!on)}>สลับ</button>
    </div>
  );
}

export const steps = ["#toggle", "#toggle"];

**実際の出力**

ข้อความเริ่มต้น: ปิดอยู่
หลังคลิกครั้งที่ 1: เปิดอยู่
หลังคลิกครั้งที่ 2: ปิดอยู่

例 12: useEffect

effectは描画の後に実行されます。データの読み込みや、コンポーネントが最初に表示されたときの準備に使います。

import React, { useEffect, useState } from "react";

export function App() {
  const [message, setMessage] = useState("กำลังโหลด");
  useEffect(() => {
    setMessage("โหลดเสร็จแล้ว (จาก useEffect)");
  }, []);
  return <p data-out>{message}</p>;
}

export const steps = [];

**実際の出力**

ข้อความเริ่มต้น: โหลดเสร็จแล้ว (จาก useEffect)

例 13: 値を保存するフォーム

inputの値をstateに読み込み、ボタンを押したときに保存します。この例では初期値を「สมชาย」にしています。

import React, { useState } from "react";

export function App() {
  const [name, setName] = useState("สมชาย");
  const [saved, setSaved] = useState("");
  return (
    <div>
      <input value={name} onChange={e => setName(e.target.value)} />
      <button id="save" onClick={() => setSaved(name)}>บันทึก</button>
      <p data-out>บันทึกแล้ว: {saved || "ยังไม่มี"}</p>
    </div>
  );
}

export const steps = ["#save"];

**実際の出力**

ข้อความเริ่มต้น: บันทึกแล้ว: ยังไม่มี
หลังคลิกครั้งที่ 1: บันทึกแล้ว: สมชาย

例 14: ToDoリスト

spreadで新しい配列を作って項目を追加します。元の配列を直接変更しません。

import React, { useState } from "react";

let nextId = 1;

export function App() {
  const [items, setItems] = useState([]);
  const add = () => {
    const id = nextId++;
    setItems([...items, { id, text: "งานที่ " + id }]);
  };
  return (
    <div>
      <button id="add" onClick={add}>เพิ่มงาน</button>
      <ul data-out>{items.map(i => <li key={i.id}>{i.text}</li>)}</ul>
    </div>
  );
}

export const steps = ["#add", "#add"];

**実際の出力**

ข้อความเริ่มต้น: 
หลังคลิกครั้งที่ 1: งานที่ 1
หลังคลิกครั้งที่ 2: งานที่ 1งานที่ 2

例 15: Context

propsを何段も渡さずに、深いところにあるコンポーネントへテーマの値を渡します。

import React, { createContext, useContext } from "react";

const ThemeContext = createContext("light");

function Button() {
  const theme = useContext(ThemeContext);
  return <button>ธีม: {theme}</button>;
}

export function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Button />
    </ThemeContext.Provider>
  );
}

**実際の出力**

<button>ธีม: dark</button>

例 16: useReducer

actionとreducerを使い、複数の値や操作を持つstateを管理します。

import React, { useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "inc": return { count: state.count + 1 };
    case "dec": return { count: state.count - 1 };
    default: return state;
  }
}

export function App() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <div>
      <p data-out>ค่า: {state.count}</p>
      <button id="inc" onClick={() => dispatch({ type: "inc" })}>+</button>
      <button id="dec" onClick={() => dispatch({ type: "dec" })}>-</button>
    </div>
  );
}

export const steps = ["#inc", "#inc", "#dec"];

**実際の出力**

ข้อความเริ่มต้น: ค่า: 0
หลังคลิกครั้งที่ 1: ค่า: 1
หลังคลิกครั้งที่ 2: ค่า: 2
หลังคลิกครั้งที่ 3: ค่า: 1

例 17: カスタムフック

stateのロジックをuseで始まる関数にまとめ、再利用できるようにします。

import React, { useState } from "react";

function useCounter(start) {
  const [count, setCount] = useState(start);
  return { count, increment: () => setCount(c => c + 1) };
}

export function App() {
  const { count, increment } = useCounter(10);
  return (
    <div>
      <p data-out>ค่าตอนนี้: {count}</p>
      <button id="inc" onClick={increment}>เพิ่ม</button>
    </div>
  );
}

export const steps = ["#inc"];

**実際の出力**

ข้อความเริ่มต้น: ค่าตอนนี้: 10
หลังคลิกครั้งที่ 1: ค่าตอนนี้: 11

例 18: useMemo

重い計算の結果を覚えておき、依存する値が変わったときだけ計算し直します。

import React, { useMemo } from "react";

const numbers = [1, 2, 3, 4, 5];

export function App() {
  const total = useMemo(() => numbers.reduce((a, b) => a + b, 0), []);
  return <p>ผลรวม = {total}</p>;
}

**実際の出力**

<p>ผลรวม = 15</p>

例 19: タブ

ユーザーが押したタブに応じて、objectから内容を選びます。この例ではタブAとBを切り替えます。

import React, { useState } from "react";

const content = {
  a: "เนื้อหาแท็บ A",
  b: "เนื้อหาแท็บ B",
};

export function App() {
  const [tab, setTab] = useState("a");
  return (
    <div>
      <button id="tab-a" onClick={() => setTab("a")}>แท็บ A</button>
      <button id="tab-b" onClick={() => setTab("b")}>แท็บ B</button>
      <p data-out>{content[tab]}</p>
    </div>
  );
}

export const steps = ["#tab-b", "#tab-a"];

**実際の出力**

ข้อความเริ่มต้น: เนื้อหาแท็บ A
หลังคลิกครั้งที่ 1: เนื้อหาแท็บ B
หลังคลิกครั้งที่ 2: เนื้อหาแท็บ A

例 20: データの読み込み

コンポーネントが最初に表示されたときにデータを読み込み、読み込み中の表示を出し、完了後にリストを表示します。

import React, { useEffect, useState } from "react";

const DATA = ["สมชาย", "สมหญิง", "สมศักดิ์"];

export function App() {
  const [loading, setLoading] = useState(true);
  const [items, setItems] = useState([]);
  const load = () => {
    setLoading(true);
    setTimeout(() => { setItems(DATA); setLoading(false); }, 0);
  };
  useEffect(() => { load(); }, []);
  return (
    <div>
      <button id="reload" onClick={load}>โหลดใหม่</button>
      <div data-out>{loading ? <p>กำลังโหลด...</p> : <ul>{items.map(n => <li key={n}>{n}</li>)}</ul>}</div>
    </div>
  );
}

export const steps = ["#reload"];

**実際の出力**

ข้อความเริ่มต้น: กำลังโหลด...
หลังคลิกครั้งที่ 1: สมชายสมหญิงสมศักดิ์

例 21: コンポーネントで作る小さなWebサイト

ヘッダーとメニュー、紹介の部分、データから作るサービスカード、フッターを一つのページにまとめます。

import React from "react";

const menu = ["หน้าแรก", "บริการ", "ติดต่อ"];
const services = [
  { id: 1, name: "เว็บไซต์องค์กร", price: 15000 },
  { id: 2, name: "ร้านค้าออนไลน์", price: 25000 },
];

function Header() {
  return (
    <header style={{ background: "#1a2b4c", color: "#fff", padding: "12px" }}>
      <strong>บริษัทตัวอย่าง</strong>
      <nav>{menu.map(m => <a key={m} href="#" style={{ marginLeft: 12, color: "#fff" }}>{m}</a>)}</nav>
    </header>
  );
}

function Hero({ title, subtitle }) {
  return (
    <section>
      <h1>{title}</h1>
      <p>{subtitle}</p>
    </section>
  );
}

function ServiceCard({ name, price }) {
  return (
    <div style={{ border: "1px solid #ccc", borderRadius: 8, padding: 12, margin: 8 }}>
      <h3>{name}</h3>
      <p>ราคาเริ่มต้น {price.toLocaleString("en-US")} บาท</p>
    </div>
  );
}

function Footer() {
  return <footer><small>© 2026 บริษัทตัวอย่าง</small></footer>;
}

export function App() {
  return (
    <div>
      <Header />
      <main>
        <Hero title="ยินดีต้อนรับ" subtitle="เราสร้างเว็บให้ธุรกิจของคุณ" />
        <div>{services.map(s => <ServiceCard key={s.id} name={s.name} price={s.price} />)}</div>
      </main>
      <Footer />
    </div>
  );
}

**実際の出力**

<div><header style="background:#1a2b4c;color:#fff;padding:12px"><strong>บริษัทตัวอย่าง</strong><nav><a href="#" style="margin-left:12px;color:#fff">หน้าแรก</a><a href="#" style="margin-left:12px;color:#fff">บริการ</a><a href="#" style="margin-left:12px;color:#fff">ติดต่อ</a></nav></header><main><section><h1>ยินดีต้อนรับ</h1><p>เราสร้างเว็บให้ธุรกิจของคุณ</p></section><div><div style="border:1px solid #ccc;border-radius:8px;padding:12px;margin:8px"><h3>เว็บไซต์องค์กร</h3><p>ราคาเริ่มต้น 15,000 บาท</p></div><div style="border:1px solid #ccc;border-radius:8px;padding:12px;margin:8px"><h3>ร้านค้าออนไลน์</h3><p>ราคาเริ่มต้น 25,000 บาท</p></div></div></main><footer><small>© 2026 บริษัทตัวอย่าง</small></footer></div>

次のステップ

次はReact Routerでページを分け、fetchで実際のAPIを呼び出し、ViteやNext.jsでプロジェクトを作成しましょう。stateとpropsが分かれば、機能の追加はコンポーネントを組み合わせる作業になります。

まとめ

Reactは、propsを受け取りstateを持つコンポーネントからページを作ります。stateが変わるとReactは影響を受ける部分だけを描き直します。21の例は表示の基本から小さなWebサイト全体の組み立てまでを扱い、この記事のすべての出力は実際の実行結果です。