React 从零开始:用 21 个示例搭建网站

React 从零开始:用 21 个示例搭建网站

React 是一个用于构建交互式网页的 JavaScript 库。它把页面拆分为称为组件的小部件,每个组件接收数据并返回页面的一部分。本文将带你从显示第一段文字开始,一直到组装一个带菜单、由数据生成的卡片以及页脚的小型网站。每个示例都是实际运行过的文件,所示输出也是真实的运行结果。

需要准备的东西

需要 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: 样式使用对象

React 的 style 接收驼峰命名的对象,例如 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 会重新渲染。本例点击按钮两次并查看变化。

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: 受控输入框

输入框的值由 state 控制,每次输入都通过 onChange 更新。本例显示初始的零个字符。

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: 开关切换

把布尔值保存在 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: 保存输入值的表单

把输入框的值读入 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: 待办列表

使用 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: 自定义 Hook

把 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: 标签页

根据用户点击的标签从对象中选择内容。本例在标签 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: 由组件组成的小型网站

把所有内容组合成一个页面:包括带菜单的页头、介绍区域、由数据生成的服务卡片以及页脚。

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 个示例涵盖了从渲染到组装完整小型网站的内容,本文中的每个输出都来自真实运行。