تعلم React من الصفر: ابنِ موقعًا مع 21 مثالًا

تعلم React من الصفر: ابنِ موقعًا مع 21 مثالًا

React مكتبة JavaScript لبناء صفحات ويب تفاعلية. تقسم الصفحة إلى قطع صغيرة تسمى المكوّنات (components)، وكل مكوّن يستقبل بيانات ويعيد جزءًا من الصفحة. تأخذك هذه المقالة من عرض أول نص إلى تجميع موقع صغير فيه قائمة وبطاقات مبنية من البيانات وتذييل. كل مثال ملف حقيقي تم تشغيله، والمخرجات المعروضة هي المخرجات الفعلية.

ما الذي تحتاجه

تحتاج إلى Node.js بالإصدار 18 أو أحدث، وأساسيات JavaScript، وأي محرر نصوص. ثبّت الحزم بالأمر npm install [email protected] [email protected]، ثم شغّل كل مثال بمشغّل الاختبار الخاص بهذه المقالة. الأمثلة التي تحتوي على steps تنقر الأزرار في متصفح محاكى مبني بـ jsdom.

رسوم توضيحية

طريقة عمل React: تدخل البيانات والحالة إلى المكوّن فينتج HTML
طريقة عمل React: تدخل البيانات والحالة إلى المكوّن فينتج HTML

المثال 1: عرض أول نص

المكوّن دالة تعيد JSX. يبني هذا المثال عنوانًا ويحوله إلى HTML باستخدام renderToStaticMarkup.

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 مفتاحًا لكي يتعرف 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 ككائن

يستقبل style في React كائنًا بأسماء بصيغة camelCase، مثل 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: عدّاد باستخدام الحالة

تحتفظ useState بقيمة تتغير، واستدعاء الدالة المحدِّثة يجعل 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
دورة الحالة: النقر يستدعي الدالة المحدِّثة فيعيد React الرسم
دورة الحالة: النقر يستدعي الدالة المحدِّثة فيعيد React الرسم

المثال 10: حقل إدخال خاضع للتحكم

تتحكم الحالة في قيمة الحقل، ويتم تحديثها في 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: مفتاح تشغيل وإيقاف

احفظ قيمة منطقية في الحالة واقلبها باستخدام !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

يعمل التأثير بعد اكتمال الرسم. استخدمه لأعمال مثل تحميل البيانات أو التهيئة عند ظهور المكوّن لأول مرة.

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: نموذج يحفظ قيمته

انقل قيمة الحقل إلى الحالة واحفظها عند الضغط على الزر. يبدأ هذا المثال بالاسم สมชาย.

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.

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 مخصص

ضع منطق الحالة داخل دالة تبدأ بـ 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، واستخدم Vite أو Next.js لإنشاء المشروع. وبعد فهم الحالة والـ props تصبح إضافة الميزات مسألة تركيب مكوّنات جديدة.

الخلاصة

تبني React الصفحات من مكوّنات تستقبل props وتحتفظ بالحالة. عند تغيّر الحالة يعيد React رسم الأجزاء المتأثرة فقط. تغطي الأمثلة الواحد والعشرون كل شيء من العرض إلى تجميع موقع صغير كامل، وكل مخرجات هذه المقالة نتيجة تشغيل فعلي.