React คืออะไร? สอน Frontend Development ตั้งแต่ Component Hook จนถึง Next.js 2026

React คืออะไร? สอน Frontend Development ตั้งแต่ Component Hook จนถึง Next.js 2026

TECH Frontend · 3500 คำ · อ่าน ~18 นาที

React คืออะไร? — Library vs Framework

React คือ JavaScript Library สำหรับสร้าง User Interface ที่พัฒนาโดย Meta (Facebook) เปิดตัวครั้งแรกในปี 2013 และเป็น Frontend Library ที่ได้รับความนิยมสูงสุดในโลกตลอดหลายปีที่ผ่านมา จุดสำคัญที่ต้องเข้าใจคือ React เป็น Library ไม่ใช่ Framework ความแตกต่างคือ Library ให้เครื่องมือเฉพาะทาง (การสร้าง UI) แล้วคุณเลือกเครื่องมืออื่นเพิ่มเอง ส่วน Framework อย่าง Angular เป็นชุดเครื่องมือครบวงจรที่กำหนดโครงสร้างและวิธีการทำงานให้ทุกอย่าง

React มี 3 แนวคิดหลักที่ทำให้แตกต่างจากเครื่องมืออื่น ได้แก่ Component-Based Architecture ที่แบ่ง UI ออกเป็นชิ้นส่วนเล็กๆ ที่จัดการตัวเองได้ Declarative Programming ที่คุณบอก React ว่าต้องการให้ UI หน้าตาเป็นอย่างไร แล้ว React จัดการ DOM ให้เอง และ Virtual DOM ที่เป็นสำเนาของ DOM จริงในหน่วยความจำ เมื่อ State เปลี่ยน React จะเปรียบเทียบ Virtual DOM เก่ากับใหม่ (Diffing Algorithm) แล้วอัพเดตเฉพาะส่วนที่เปลี่ยนแปลงจริงบน DOM จริง ทำให้ Performance ดีขึ้นอย่างมาก

ทำไมต้องเรียน React ในปี 2026?
1. ตลาดงาน — ตำแหน่ง Frontend Developer ส่วนใหญ่ต้องการ React เป็นอันดับหนึ่ง
2. Ecosystem — มี Library, เครื่องมือ, และ Community ใหญ่ที่สุดในโลก Frontend
3. React Native — สามารถนำความรู้ React ไปสร้าง Mobile App ได้ทันที
4. Next.js — Full-stack Framework บน React ที่กลายเป็นมาตรฐานอุตสาหกรรม
5. Server Components — แนวคิดใหม่ที่ปฏิวัติวิธีการ Render ใน React 19+

ติดตั้ง React — เริ่มต้นโปรเจกต์ใหม่

ในปี 2026 มีหลายวิธีในการเริ่มต้นโปรเจกต์ React แต่ละวิธีเหมาะกับสถานการณ์ที่แตกต่างกัน

Vite — เครื่องมือสมัยใหม่ที่เร็วที่สุด

Vite (อ่านว่า "วีท") เป็น Build Tool ที่พัฒนาโดย Evan You ผู้สร้าง Vue.js แต่รองรับ React ด้วย จุดเด่นคือ Dev Server ที่เริ่มต้นเร็วมาก (ใช้ Native ESM) และ Hot Module Replacement (HMR) ที่อัพเดตทันทีเมื่อแก้ไขโค้ด เป็นตัวเลือกแนะนำอันดับหนึ่งสำหรับโปรเจกต์ React ที่ไม่ต้องการ SSR

# สร้างโปรเจกต์ React ด้วย Vite
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev

# โครงสร้างไฟล์ที่ได้
my-app/
├── public/
│   └── vite.svg
├── src/
│   ├── App.tsx           # Component หลัก
│   ├── App.css           # สไตล์ของ App
│   ├── main.tsx          # Entry point
│   ├── index.css         # Global styles
│   └── vite-env.d.ts     # TypeScript declarations
├── index.html            # HTML template
├── package.json
├── tsconfig.json
└── vite.config.ts        # Vite configuration

Next.js — Full-stack React Framework

Next.js เป็น Framework ที่สร้างบน React โดย Vercel เพิ่มความสามารถ Server-Side Rendering (SSR), Static Site Generation (SSG), API Routes และอีกมากมาย เป็นตัวเลือกที่ดีที่สุดสำหรับโปรเจกต์ที่ต้องการ SEO, Performance สูง หรือต้องการ Full-stack capabilities ทีม React เองก็แนะนำให้ใช้ Next.js สำหรับโปรเจกต์ใหม่

# สร้างโปรเจกต์ Next.js
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm run dev

# โครงสร้าง App Router
my-app/
├── app/
│   ├── layout.tsx        # Root layout
│   ├── page.tsx          # หน้าแรก (/)
│   ├── globals.css
│   ├── about/
│   │   └── page.tsx      # หน้า /about
│   └── blog/
│       ├── page.tsx      # หน้า /blog
│       └── [slug]/
│           └── page.tsx  # Dynamic route /blog/:slug
├── components/
├── public/
└── next.config.ts

Create React App — ยังใช้ได้แต่ไม่แนะนำ

Create React App (CRA) เคยเป็นเครื่องมือมาตรฐานสำหรับเริ่มต้นโปรเจกต์ React แต่ในปี 2026 ทีม React แนะนำให้ใช้ Vite หรือ Next.js แทน เนื่องจาก CRA ช้ากว่ามากและไม่ได้รับการพัฒนาอย่างต่อเนื่อง

JSX Syntax — เขียน HTML ใน JavaScript

JSX (JavaScript XML) คือส่วนขยายของ JavaScript ที่ให้เขียน HTML-like syntax ใน JavaScript ได้ JSX ไม่ใช่ HTML จริงๆ แต่จะถูกแปลงเป็น React.createElement() calls โดย Babel หรือ SWC JSX ทำให้โค้ดอ่านง่ายขึ้นมากเมื่อเปรียบเทียบกับการเรียก createElement ด้วยตัวเอง

// JSX — อ่านง่าย
const element = (
  <div className="card">
    <h2>สวัสดี React</h2>
    <p>ยินดีต้อนรับ</p>
  </div>
);

// ถูกแปลงเป็น (ไม่ต้องเขียนเอง)
const element = React.createElement(
  'div',
  { className: 'card' },
  React.createElement('h2', null, 'สวัสดี React'),
  React.createElement('p', null, 'ยินดีต้อนรับ')
);

// กฎสำคัญของ JSX
// 1. ต้องมี Root Element เดียว (ใช้ <Fragment> หรือ <> ได้)
// 2. ใช้ className แทน class
// 3. ใช้ htmlFor แทน for
// 4. ปิด Tag ทุกตัว เช่น <img /> <br />
// 5. ใช้ {} สำหรับ JavaScript Expression

Components — หัวใจของ React

Component คือชิ้นส่วนอิสระของ UI ที่สามารถ Reuse ได้ ลองจินตนาการเว็บไซต์ e-commerce หน้าแรกประกอบด้วย Header, SearchBar, ProductCard หลายตัว, Sidebar, Footer แต่ละชิ้นส่วนเหล่านี้คือ Component ที่มีโค้ด HTML, CSS และ Logic ของตัวเอง สามารถพัฒนาและทดสอบแยกจากกันได้ และนำไปใช้ซ้ำในหลายหน้า

Function Component — มาตรฐานในปัจจุบัน

ในปี 2026 React ใช้ Function Component เป็นหลัก Class Component ยังใช้ได้แต่ไม่แนะนำสำหรับโค้ดใหม่ Function Component เรียบง่ายกว่าและรองรับ Hooks ซึ่งเป็นระบบ State Management หลักของ React

// Function Component พื้นฐาน
function Greeting() {
  return <h1>สวัสดีครับ</h1>;
}

// Arrow Function (เขียนได้เหมือนกัน)
const Greeting = () => <h1>สวัสดีครับ</h1>;

// Component ที่ซับซ้อนขึ้น
function UserCard() {
  const name = "สมชาย";
  const role = "Full Stack Developer";
  const isOnline = true;

  return (
    <div className="user-card">
      <div className="avatar">
        {name.charAt(0)}
      </div>
      <h3>{name}</h3>
      <p>{role}</p>
      <span className={isOnline ? 'online' : 'offline'}>
        {isOnline ? 'ออนไลน์' : 'ออฟไลน์'}
      </span>
    </div>
  );
}

Props — ส่งข้อมูลระหว่าง Components

Props (Properties) คือวิธีส่งข้อมูลจาก Parent Component ไปยัง Child Component เปรียบเหมือน Parameter ของ Function Props เป็น Read-only คือ Child ไม่สามารถแก้ไข Props ที่ได้รับ ถ้าต้องการเปลี่ยนค่า ต้องใช้ State แทน

// กำหนด Props ด้วย TypeScript
interface ProductCardProps {
  name: string;
  price: number;
  image: string;
  discount?: number;       // Optional prop
  onAddToCart: () => void; // Function prop
}

function ProductCard({ name, price, image, discount, onAddToCart }: ProductCardProps) {
  const finalPrice = discount ? price * (1 - discount / 100) : price;

  return (
    <div className="product-card">
      <img src={image} alt={name} />
      <h3>{name}</h3>
      {discount && <span className="badge">ลด {discount}%</span>}
      <p className="price">
        {discount && <s>฿{price.toLocaleString()}</s>}
        ฿{finalPrice.toLocaleString()}
      </p>
      <button onClick={onAddToCart}>เพิ่มลงตะกร้า</button>
    </div>
  );
}

// ใช้งาน
function Shop() {
  return (
    <div>
      <ProductCard
        name="iPhone 17 Pro"
        price={49900}
        image="/iphone.jpg"
        discount={10}
        onAddToCart={() => console.log('Added!')}
      />
    </div>
  );
}

Children Props — Component ซ้อนกัน

// Layout Component ที่รับ children
function Card({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// ใช้งาน — ใส่อะไรก็ได้ใน Card
<Card title="ข้อมูลผู้ใช้">
  <p>ชื่อ: สมชาย</p>
  <p>อายุ: 25</p>
  <button>แก้ไข</button>
</Card>

State & useState — จัดการข้อมูลที่เปลี่ยนแปลง

State คือข้อมูลภายใน Component ที่สามารถเปลี่ยนแปลงได้ตลอดเวลา เมื่อ State เปลี่ยน React จะ Re-render Component นั้นโดยอัตโนมัติ ลองนึกถึงตัวนับ (Counter) เมื่อกดปุ่ม + ตัวเลขต้องเพิ่มขึ้น ค่าตัวเลขนี้คือ State เพราะมันเปลี่ยนแปลงได้ ส่วนข้อความ "จำนวน:" เป็นข้อมูลคงที่ ไม่ต้องเป็น State

import { useState } from 'react';

function Counter() {
  // useState คืน Array 2 ตัว: [ค่าปัจจุบัน, ฟังก์ชันอัพเดต]
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>จำนวน: {count}</h2>
      <button onClick={() => setCount(count + 1)}>เพิ่ม</button>
      <button onClick={() => setCount(count - 1)}>ลด</button>
      <button onClick={() => setCount(0)}>รีเซ็ต</button>
    </div>
  );
}

// Functional Update — ใช้เมื่อค่าใหม่ขึ้นกับค่าเก่า
function SafeCounter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    // ผิด: ถ้ากดเร็วๆ อาจข้ามการนับ
    // setCount(count + 1);

    // ถูก: ใช้ Functional Update
    setCount(prev => prev + 1);
  };

  return <button onClick={increment}>นับ: {count}</button>;
}

// State กับ Object
function UserForm() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });

  const handleChange = (field: string, value: string | number) => {
    setUser(prev => ({
      ...prev,        // Spread ค่าเก่า
      [field]: value   // อัพเดตเฉพาะ field ที่เปลี่ยน
    }));
  };

  return (
    <form>
      <input
        value={user.name}
        onChange={e => handleChange('name', e.target.value)}
        placeholder="ชื่อ"
      />
      <input
        value={user.email}
        onChange={e => handleChange('email', e.target.value)}
        placeholder="อีเมล"
      />
    </form>
  );
}

useEffect — Side Effects & Lifecycle

useEffect ใช้สำหรับจัดการ Side Effects ใน Component เช่น การเรียก API, การ Subscribe Event, การตั้ง Timer, การแก้ไข DOM โดยตรง สิ่งเหล่านี้เป็นงานที่อยู่นอกเหนือจากการ Render UI จึงเรียกว่า "Side Effect" useEffect ทำงานหลังจาก Component Render เสร็จแล้ว ทำให้ไม่ Block การแสดงผล UI

import { useState, useEffect } from 'react';

// 1. ทำงานทุกครั้งที่ Re-render (ไม่ค่อยใช้)
useEffect(() => {
  console.log('Component rendered');
});

// 2. ทำงานครั้งเดียวตอน Mount ([] = empty dependency)
useEffect(() => {
  console.log('Component mounted');
  fetchData();  // เรียก API ตอนเปิดหน้า
}, []);

// 3. ทำงานเมื่อ dependency เปลี่ยน
useEffect(() => {
  console.log(`Count changed to ${count}`);
  document.title = `Count: ${count}`;
}, [count]);

// 4. Cleanup Function — ทำงานตอน Unmount
useEffect(() => {
  const timer = setInterval(() => {
    setSeconds(s => s + 1);
  }, 1000);

  // Cleanup: ล้าง Timer เมื่อ Component หายไป
  return () => clearInterval(timer);
}, []);

// ตัวอย่างจริง: Fetch Data จาก API
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    async function fetchUsers() {
      try {
        const res = await fetch('/api/users', {
          signal: controller.signal
        });
        if (!res.ok) throw new Error('Failed to fetch');
        const data = await res.json();
        setUsers(data);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    }

    fetchUsers();

    // Cleanup: ยกเลิก Request ถ้า Component Unmount
    return () => controller.abort();
  }, []);

  if (loading) return <p>กำลังโหลด...</p>;
  if (error) return <p>เกิดข้อผิดพลาด: {error}</p>;
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

Event Handling — จัดการเหตุการณ์

React จัดการ Events ผ่านระบบ Synthetic Events ซึ่งเป็น Wrapper รอบ Native DOM Events ให้ทำงานเหมือนกันทุก Browser ชื่อ Event ใน React ใช้ camelCase เช่น onClick onChange onSubmit แทน onclick onchange onsubmit

function EventExamples() {
  // Click Event
  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault();
    console.log('Button clicked!');
  };

  // Input Change Event
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log('Value:', e.target.value);
  };

  // Form Submit Event
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    // ส่งข้อมูล
  };

  // Keyboard Event
  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed!');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} onKeyDown={handleKeyDown} />
      <button onClick={handleClick}>ส่ง</button>
    </form>
  );
}

Conditional Rendering & Lists — แสดงผลตามเงื่อนไข

React ใช้ JavaScript Expression ปกติในการแสดงผลตามเงื่อนไข ไม่มี Directive พิเศษเหมือน Angular (ngIf) หรือ Vue (v-if) ทำให้ยืดหยุ่นมากและใช้ความรู้ JavaScript ที่มีอยู่แล้ว

function Dashboard({ user, notifications }) {
  return (
    <div>
      {/* 1. Ternary Operator — เลือก 1 จาก 2 */}
      {user ? <UserProfile user={user} /> : <LoginForm />}

      {/* 2. && — แสดงเมื่อเป็น true */}
      {notifications.length > 0 && (
        <Badge count={notifications.length} />
      )}

      {/* 3. Render List ด้วย map() — ต้องมี key */}
      <ul>
        {notifications.map(noti => (
          <li key={noti.id}>
            <span>{noti.message}</span>
            <time>{noti.date}</time>
          </li>
        ))}
      </ul>

      {/* 4. Early Return — กรณีพิเศษ */}
      {notifications.length === 0 && (
        <p className="empty">ไม่มีการแจ้งเตือน</p>
      )}
    </div>
  );
}

// key ต้องเป็นค่า Unique และคงที่ ห้ามใช้ index เป็น key
// ถ้า List มีการเพิ่ม/ลบ/เรียงลำดับ เพราะจะทำให้ React สับสน

Forms — Controlled vs Uncontrolled

การจัดการ Form ใน React มี 2 แนวทาง คือ Controlled Component ที่ React เป็นคนจัดการค่าของ Input ทุกตัว และ Uncontrolled Component ที่ให้ DOM จัดการเอง แล้วดึงค่าด้วย ref เมื่อต้องการ Controlled Component เป็นแนวทางที่แนะนำเพราะควบคุมได้มากกว่า Validate ได้ทันที และทำ Conditional Logic ได้ง่าย

import { useState, useRef } from 'react';

// Controlled Component — React จัดการทุกอย่าง
function ControlledForm() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: '',
  });
  const [errors, setErrors] = useState<Record<string, string>>({});

  const validate = () => {
    const newErrors: Record<string, string> = {};
    if (!formData.username) newErrors.username = 'กรุณากรอกชื่อผู้ใช้';
    if (!formData.email.includes('@')) newErrors.email = 'อีเมลไม่ถูกต้อง';
    if (formData.password.length < 8) newErrors.password = 'รหัสผ่านต้องมีอย่างน้อย 8 ตัว';
    return newErrors;
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const newErrors = validate();
    if (Object.keys(newErrors).length === 0) {
      // ส่งข้อมูล
      console.log('Submit:', formData);
    } else {
      setErrors(newErrors);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={formData.username}
        onChange={e => setFormData(prev => ({ ...prev, username: e.target.value }))}
      />
      {errors.username && <span className="error">{errors.username}</span>}
      <button type="submit">สมัครสมาชิก</button>
    </form>
  );
}

// Uncontrolled Component — ใช้ ref ดึงค่า
function UncontrolledForm() {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Value:', inputRef.current?.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="ค่าเริ่มต้น" />
      <button type="submit">ส่ง</button>
    </form>
  );
}

useContext & Context API — แชร์ข้อมูลข้าม Components

Context API แก้ปัญหา Prop Drilling คือการส่ง Props ผ่านหลายชั้นของ Component ที่ไม่ได้ใช้ Props นั้นเลย แค่ส่งต่อเท่านั้น เช่น App → Layout → Sidebar → UserMenu → Avatar ถ้า Avatar ต้องการข้อมูล User ต้องส่ง Props ผ่าน 4 ชั้น ด้วย Context API สามารถ "กระจาย" ข้อมูลจาก Provider ให้ Component ลูกทุกระดับเข้าถึงได้โดยตรง

import { createContext, useContext, useState, ReactNode } from 'react';

// 1. สร้าง Context
interface AuthContextType {
  user: { name: string; email: string } | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
}

const AuthContext = createContext<AuthContextType | null>(null);

// 2. สร้าง Provider Component
function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState(null);

  const login = async (email: string, password: string) => {
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const data = await res.json();
    setUser(data.user);
  };

  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout, isAuthenticated: !!user }}>
      {children}
    </AuthContext.Provider>
  );
}

// 3. Custom Hook สำหรับใช้งาน Context
function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuth must be used within AuthProvider');
  return context;
}

// 4. ใช้งานใน Component — ไม่ต้อง Prop Drilling
function Navbar() {
  const { user, logout, isAuthenticated } = useAuth();

  return (
    <nav>
      {isAuthenticated ? (
        <>
          <span>สวัสดี {user.name}</span>
          <button onClick={logout}>ออกจากระบบ</button>
        </>
      ) : (
        <a href="/login">เข้าสู่ระบบ</a>
      )}
    </nav>
  );
}

Custom Hooks — สร้าง Hook ของตัวเอง

Custom Hooks คือการแยก Logic ที่ใช้ซ้ำออกมาเป็น Function ที่มีชื่อขึ้นต้นด้วย use เป็นวิธีที่ทรงพลังที่สุดในการ Reuse Logic ระหว่าง Components โดยไม่ต้องเปลี่ยนโครงสร้าง Component เช่น Logic การ Fetch Data, จัดการ Form, Local Storage, Media Query, Debounce ล้วนสร้างเป็น Custom Hook ได้

// Custom Hook: useFetch — ใช้ซ้ำสำหรับ API Call
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);

    fetch(url, { signal: controller.signal })
      .then(res => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
      })
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') setError(err.message);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// ใช้งาน
function UserProfile({ userId }: { userId: string }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
  if (loading) return <Spinner />;
  if (error) return <ErrorMessage message={error} />;
  return <div>{user.name}</div>;
}

// Custom Hook: useLocalStorage — เก็บ State ลง LocalStorage
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

// Custom Hook: useDebounce — หน่วง Search
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

React Router — Client-side Routing

React Router เป็น Library สำหรับจัดการ Routing ใน Single Page Application (SPA) ทำให้สามารถนำทางระหว่างหน้าต่างๆ โดยไม่ต้อง Reload หน้า React Router v6+ ใช้ Data Router ที่รองรับ Loader และ Action สำหรับจัดการ Data Fetching ใน Route

import { BrowserRouter, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';

// กำหนด Routes
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">หน้าแรก</Link>
        <Link to="/products">สินค้า</Link>
        <Link to="/about">เกี่ยวกับ</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/products" element={<ProductList />} />
        <Route path="/products/:id" element={<ProductDetail />} />
        <Route path="/about" element={<About />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// Dynamic Route — ใช้ useParams
function ProductDetail() {
  const { id } = useParams();
  const navigate = useNavigate();

  return (
    <div>
      <h1>สินค้า #{id}</h1>
      <button onClick={() => navigate('/products')}>กลับ</button>
      <button onClick={() => navigate(-1)}>ย้อนกลับ</button>
    </div>
  );
}

// Protected Route — ต้อง Login ก่อน
function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { isAuthenticated } = useAuth();
  if (!isAuthenticated) return <Navigate to="/login" replace />;
  return children;
}

<Route path="/dashboard" element={
  <ProtectedRoute>
    <Dashboard />
  </ProtectedRoute>
} />

State Management — Redux Toolkit, Zustand, Jotai

เมื่อ Application มีขนาดใหญ่ การจัดการ State ด้วย useState และ useContext อาจไม่เพียงพอ จำเป็นต้องใช้ State Management Library ที่ออกแบบมาสำหรับ Global State โดยเฉพาะ ในปี 2026 มี 3 ตัวเลือกยอดนิยม

Libraryขนาดแนวคิดเหมาะกับ
Redux Toolkit~11KBFlux (Single store, Reducers)แอปขนาดใหญ่ที่ต้องการ Predictable state
Zustand~1.5KBSimple Storeแอปขนาดเล็ก-กลาง ต้องการความเรียบง่าย
Jotai~2KBAtomic StateState ที่ต้องการ Fine-grained reactivity
// Redux Toolkit — มาตรฐานสำหรับแอปขนาดใหญ่
import { createSlice, configureStore } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], total: 0 },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload);
      state.total += action.payload.price;
    },
    removeItem: (state, action) => {
      const idx = state.items.findIndex(i => i.id === action.payload);
      if (idx !== -1) {
        state.total -= state.items[idx].price;
        state.items.splice(idx, 1);
      }
    },
  },
});

// Zustand — เรียบง่ายที่สุด
import { create } from 'zustand';

const useCartStore = create((set) => ({
  items: [],
  total: 0,
  addItem: (item) => set((state) => ({
    items: [...state.items, item],
    total: state.total + item.price,
  })),
  removeItem: (id) => set((state) => {
    const item = state.items.find(i => i.id === id);
    return {
      items: state.items.filter(i => i.id !== id),
      total: state.total - (item?.price || 0),
    };
  }),
})));

// ใช้งาน Zustand — ง่ายมาก
function CartIcon() {
  const total = useCartStore(state => state.total);
  const items = useCartStore(state => state.items);
  return <span>🛒 {items.length} (฿{total})</span>;
}

Styling — CSS Modules, Tailwind CSS, Styled Components

การจัดการ CSS ใน React มีหลายแนวทาง แต่ละแนวทางมีข้อดีข้อเสียต่างกัน

CSS Modules — Scoped CSS

/* Button.module.css */
.button {
  padding: 8px 16px;
  border-radius: 6px;
  font-weight: 600;
}
.primary {
  background: #61DAFB;
  color: #000;
}

// Button.tsx
import styles from './Button.module.css';

function Button({ variant = 'primary', children }) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      {children}
    </button>
  );
}

Tailwind CSS — Utility-First CSS Framework

Tailwind CSS เป็น CSS Framework ที่ได้รับความนิยมสูงสุดในปี 2026 แนวคิดคือใช้ Utility Classes แทนการเขียน CSS เอง ข้อดีคือพัฒนาเร็ว ไม่ต้องตั้งชื่อ Class และ CSS Bundle เล็กเพราะ Tree-shaking ลบ Class ที่ไม่ได้ใช้ออก Tailwind CSS v4 ที่เพิ่งออกมาใช้ Lightning CSS Engine ที่เร็วขึ้น 10 เท่า

// Tailwind CSS — ใช้ Utility Classes
function ProductCard({ name, price, image }) {
  return (
    <div className="bg-white rounded-xl shadow-lg overflow-hidden
                    hover:shadow-xl transition-shadow duration-300
                    dark:bg-gray-800">
      <img src={image} alt={name}
           className="w-full h-48 object-cover" />
      <div className="p-4">
        <h3 className="text-lg font-semibold text-gray-800
                       dark:text-white">{name}</h3>
        <p className="text-2xl font-bold text-blue-600 mt-2">
          ฿{price.toLocaleString()}
        </p>
        <button className="mt-3 w-full bg-blue-600 text-white py-2
                           rounded-lg hover:bg-blue-700
                           active:scale-95 transition-all">
          เพิ่มลงตะกร้า
        </button>
      </div>
    </div>
  );
}

Styled Components — CSS-in-JS

import styled from 'styled-components';

const Card = styled.div`
  background: ${props => props.theme.cardBg};
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);

  &:hover {
    transform: translateY(-2px);
  }
`;

const Title = styled.h3`
  color: ${props => props.theme.text};
  font-size: 18px;
`;

// ใช้งาน
function ProductCard() {
  return (
    <Card>
      <Title>สินค้า</Title>
    </Card>
  );
}

Next.js — Full-stack React Framework

Next.js ไม่ใช่แค่ React ที่มี SSR เท่านั้น แต่เป็น Full-stack Framework ที่มีทุกอย่างที่ต้องการสำหรับ Production ตั้งแต่ Routing, Data Fetching, API Routes, Middleware, Image Optimization, Font Optimization จนถึง Deployment

App Router & Server Components

App Router (เปิดตัวใน Next.js 13+) ใช้ File-based Routing ที่แต่ละ Folder ใน app/ Directory คือ Route และ Component ทุกตัวเป็น Server Component โดยค่าเริ่มต้น คือ Render บน Server ส่ง HTML ไปที่ Browser ไม่ส่ง JavaScript ไม่มี useState ไม่มี useEffect ถ้าต้องการ Interactivity ต้องเพิ่ม 'use client' ที่ด้านบนไฟล์

// app/page.tsx — Server Component (ค่าเริ่มต้น)
// สามารถ fetch data ตรงๆ ไม่ต้อง useEffect
async function HomePage() {
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();

  return (
    <main>
      <h1>สินค้าทั้งหมด</h1>
      {products.map(p => <ProductCard key={p.id} product={p} />)}
    </main>
  );
}

// app/components/AddToCartButton.tsx — Client Component
'use client';
import { useState } from 'react';

function AddToCartButton({ productId }: { productId: string }) {
  const [added, setAdded] = useState(false);

  return (
    <button onClick={() => setAdded(true)}>
      {added ? 'เพิ่มแล้ว ✓' : 'เพิ่มลงตะกร้า'}
    </button>
  );
}

// SSR vs SSG vs ISR
// SSR — ทุก Request render ใหม่
export const dynamic = 'force-dynamic';

// SSG — สร้าง HTML ตอน Build (เร็วที่สุด)
export const dynamic = 'force-static';

// ISR — Revalidate ทุก 60 วินาที
export const revalidate = 60;

API Routes — สร้าง Backend ใน Next.js

// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const category = searchParams.get('category');

  const products = await db.product.findMany({
    where: category ? { category } : undefined,
  });

  return NextResponse.json(products);
}

export async function POST(request: NextRequest) {
  const body = await request.json();

  const product = await db.product.create({
    data: body,
  });

  return NextResponse.json(product, { status: 201 });
}

TypeScript + React — Type Safety

TypeScript เป็นมาตรฐานสำหรับ React ในปี 2026 ช่วยจับ Bug ตั้งแต่ตอนเขียนโค้ด ไม่ต้องรอ Runtime ทำให้ Refactor ได้มั่นใจ และ IDE ช่วยเสนอ Autocomplete ที่ถูกต้อง

// กำหนด Type สำหรับ Props
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
  onClick?: () => void;
  children: React.ReactNode;
}

function Button({
  variant,
  size = 'md',
  disabled = false,
  loading = false,
  onClick,
  children,
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant} btn-${size}`}
      disabled={disabled || loading}
      onClick={onClick}
    >
      {loading ? <Spinner /> : children}
    </button>
  );
}

// Generic Component
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, i) => (
        <li key={keyExtractor(item)}>{renderItem(item, i)}</li>
      ))}
    </ul>
  );
}

Testing — React Testing Library & Vitest

การ Test React Component ใน 2026 ใช้ React Testing Library ร่วมกับ Vitest เป็นหลัก แนวคิดของ React Testing Library คือ "Test เหมือนผู้ใช้จริง" คือ Query Element ด้วย Text, Role, Label ที่ User เห็น ไม่ใช่ className หรือ test-id

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import Counter from './Counter';

describe('Counter', () => {
  it('แสดงค่าเริ่มต้น 0', () => {
    render(<Counter />);
    expect(screen.getByText('จำนวน: 0')).toBeInTheDocument();
  });

  it('เพิ่มค่าเมื่อกดปุ่มเพิ่ม', () => {
    render(<Counter />);
    fireEvent.click(screen.getByText('เพิ่ม'));
    expect(screen.getByText('จำนวน: 1')).toBeInTheDocument();
  });

  it('ลดค่าเมื่อกดปุ่มลด', () => {
    render(<Counter />);
    fireEvent.click(screen.getByText('เพิ่ม'));
    fireEvent.click(screen.getByText('ลด'));
    expect(screen.getByText('จำนวน: 0')).toBeInTheDocument();
  });

  it('รีเซ็ตค่าเป็น 0', () => {
    render(<Counter />);
    fireEvent.click(screen.getByText('เพิ่ม'));
    fireEvent.click(screen.getByText('เพิ่ม'));
    fireEvent.click(screen.getByText('รีเซ็ต'));
    expect(screen.getByText('จำนวน: 0')).toBeInTheDocument();
  });
});

// Test Component ที่เรียก API
describe('UserList', () => {
  it('แสดงรายชื่อ User หลัง fetch สำเร็จ', async () => {
    // Mock fetch
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve([
        { id: 1, name: 'สมชาย' },
        { id: 2, name: 'สมหญิง' },
      ]),
    });

    render(<UserList />);
    expect(screen.getByText('กำลังโหลด...')).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByText('สมชาย')).toBeInTheDocument();
      expect(screen.getByText('สมหญิง')).toBeInTheDocument();
    });
  });
});

ข้อผิดพลาดที่พบบ่อยในการเขียน React

ข้อผิดพลาดปัญหาวิธีแก้
แก้ไข State โดยตรงstate.items.push(item) — React ไม่ Re-renderใช้ setItems([...items, item]) สร้าง Array ใหม่
ลืม key ใน ListReact แยกไม่ออกว่า Item ไหนเปลี่ยนใส่ key={item.id} ที่ Unique
useEffect dependency ผิดInfinite loop หรือ Stale dataใส่ทุกตัวแปรที่ใช้ใน Dependency Array
Prop Drilling มากเกินไปโค้ดซับซ้อน ยากต่อการ Maintainใช้ Context API หรือ State Management Library
ไม่ Cleanup useEffectMemory leak, Zombie subscriptionsReturn cleanup function จาก useEffect
ใช้ index เป็น keyBug เมื่อ Add/Remove/Reorder itemsใช้ Unique ID จาก Data
State update ใน renderInfinite re-render loopย้ายไปอยู่ใน useEffect หรือ Event Handler

FAQ — คำถามที่พบบ่อยเกี่ยวกับ React

Q: React กับ Vue กับ Angular เลือกตัวไหนดี?

A: ขึ้นอยู่กับสถานการณ์ React เหมาะที่สุดถ้าต้องการ Ecosystem ที่ใหญ่ที่สุด ตลาดงานเยอะที่สุด และมี Library ให้เลือกมากที่สุด Vue เหมาะสำหรับมือใหม่ที่ต้องการเริ่มต้นเร็ว Learning Curve ต่ำ Documentation ดีมาก Angular เหมาะกับ Enterprise Application ที่ต้องการ Framework ครบวงจร มี TypeScript มาให้ตั้งแต่แรก ถ้าไม่แน่ใจ เลือก React เพราะตลาดงานกว้างที่สุดและสามารถต่อยอดไป React Native ได้

Q: ควรเรียน JavaScript ก่อนหรือกระโดดเรียน React เลย?

A: ต้องเรียน JavaScript ก่อนอย่างน้อยพื้นฐาน ได้แก่ Variables, Functions, Arrays, Objects, Arrow Functions, Destructuring, Spread Operator, Promises, async/await, Array methods (map, filter, reduce) เพราะ React ใช้ JavaScript เหล่านี้ตลอดเวลา ถ้าไม่เข้าใจพื้นฐาน จะสับสนมากเมื่อเรียน React

Q: ใช้ Create React App (CRA) ได้อยู่ไหม?

A: ยังใช้ได้แต่ไม่แนะนำสำหรับโปรเจกต์ใหม่ ทีม React เองแนะนำให้ใช้ Next.js สำหรับโปรเจกต์ที่ต้องการ Full-stack หรือ Vite สำหรับ SPA ที่ไม่ต้องการ SSR เนื่องจาก CRA ใช้ Webpack ที่ช้ากว่า Vite มากและไม่ได้รับการพัฒนาอย่างต่อเนื่อง

Q: Server Component กับ Client Component ต่างกันอย่างไร?

A: Server Component Render บน Server เท่านั้น ไม่ส่ง JavaScript ไปที่ Browser สามารถ Access Database, File System ได้โดยตรง เหมาะสำหรับ Static Content Client Component ต้องเพิ่ม 'use client' ส่ง JavaScript ไปที่ Browser เพื่อ Interactivity ใช้ useState, useEffect, Event Handlers ได้ ในทางปฏิบัติ ให้เริ่มจาก Server Component แล้วเปลี่ยนเป็น Client Component เฉพาะส่วนที่ต้องการ Interactivity

Q: ต้องใช้ Redux ทุกโปรเจกต์ไหม?

A: ไม่จำเป็น โปรเจกต์เล็ก-กลางใช้ useState + useContext ก็เพียงพอ ถ้าต้องการ Global State ง่ายๆ ลอง Zustand ที่เรียบง่ายมาก ใช้ Redux Toolkit เมื่อแอปมีขนาดใหญ่ มี State ซับซ้อน ต้องการ DevTools ที่ดี และมีหลายคนทำงานร่วมกัน