React ฉบับเริ่มต้น: สร้างเว็บแอปพลิเคชันสมัยใหม่ตั้งแต่แนวคิดสู่การทดสอบ (Workshop)

สารบัญ

บริบทของคำว่า Framework และ Library

ก่อนจะเจาะลึกเรื่อง React เรามาทำความเข้าใจคำสองคำที่มักจะได้ยินบ่อยๆ ในโลกของการพัฒนาเว็บนั่นคือ Framework และ Library การเข้าใจความแตกต่างนี้จะช่วยให้เห็นภาพว่า React อยู่ตรงไหนในระบบนิเวศของการพัฒนา

Framework vs. Library: ใครเรียกใคร?

ลองจินตนาการว่าคุณกำลังสร้างบ้าน

แผนภาพแสดงความแตกต่างระหว่าง Framework และ Library

สรุปคือ React เป็น Library ที่มีความยืดหยุ่นสูง ให้อิสระแก่นักพัฒนาในการเลือกเครื่องมืออื่นๆ มาประกอบร่างเป็นแอปพลิเคชัน ในขณะที่ Framework จะมอบโครงสร้างและเครื่องมือที่ครบครันมาให้ตั้งแต่ต้น แต่ก็ต้องแลกมากับความยืดหยุ่นที่น้อยกว่า

ภาพรวมและเป้าหมายของบทความ

ในยุคที่เว็บแอปพลิเคชันมีความซับซ้อนและต้องการประสบการณ์ผู้ใช้ (User Experience) ที่รวดเร็วและโต้ตอบได้ทันที การเลือกเครื่องมือที่เหมาะสมจึงเป็นกุญแจสำคัญสู่ความสำเร็จ React ซึ่งเป็นไลบรารี JavaScript ที่สร้างและดูแลโดย Meta (Facebook) ได้ก้าวขึ้นมาเป็นหนึ่งในเครื่องมือแถวหน้าสำหรับนักพัฒนา Frontend ทั่วโลก บทความนี้จะพาคุณดำดิ่งสู่โลกของ React ตั้งแต่แนวคิดพื้นฐานที่ทำให้มันทรงพลัง ไปจนถึงการลงมือสร้างแอปพลิเคชันจริงผ่าน Workshop ที่ทำตามได้ง่าย และปิดท้ายด้วยการเขียนเทสต์เพื่อรับประกันคุณภาพของโค้ด

ทำไมต้อง React? และดีกว่า jQuery อย่างไร?

เหตุผลที่ React ได้รับความนิยมอย่างล้นหลามไม่ได้เกิดขึ้นโดยบังเอิญ แต่มาจากปรัชญาการออกแบบและระบบนิเวศ (Ecosystem) ที่แข็งแกร่งซึ่งตอบโจทย์การพัฒนาสมัยใหม่ได้อย่างลงตัว:

ก้าวข้ามจาก jQuery สู่ React

ในอดีต jQuery เคยเป็นเครื่องมือสำคัญที่ช่วยให้การจัดการ DOM และการสร้าง Interaction บนเว็บทำได้ง่ายขึ้น อย่างไรก็ตาม เมื่อแอปพลิเคชันมีความซับซ้อนมากขึ้น การจัดการ UI ด้วย jQuery เริ่มแสดงให้เห็นข้อจำกัด โดยเฉพาะการที่ต้องเขียนโค้ดเพื่อ "สั่ง" ให้ DOM เปลี่ยนแปลงทีละขั้นตอน (Imperative) ซึ่งทำให้โค้ดซับซ้อนและดูแลรักษายาก

React แก้ปัญหานี้ด้วยแนวทางที่แตกต่างอย่างสิ้นเชิง:

แผนภาพเปรียบเทียบการทำงานระหว่าง jQuery และ React
ข้อมูลจาก: BrowserStack (เม.ย. 2025) และ State of JS 2024

สิ่งที่เราจะสร้าง (Workshop)

ทฤษฎีเพียงอย่างเดียวอาจไม่เห็นภาพ เพื่อให้คุณเข้าใจการทำงานของ React อย่างแท้จริง เราจะลงมือสร้างแอปพลิเคชัน "รายการสิ่งที่ต้องทำ" (Todo List) ไปพร้อมกันทีละขั้นตอน แอปพลิเคชันนี้แม้จะดูเรียบง่าย แต่ครอบคลุมแนวคิดหลักที่สำคัญของ React ไว้อย่างครบถ้วน:

Workshop นี้จะนำคุณผ่านกระบวนการคิดแบบ "Thinking in React" ซึ่งเป็นทักษะสำคัญที่จะช่วยให้คุณสามารถออกแบบและสร้างแอปพลิเคชัน React อื่นๆ ในอนาคตได้อย่างเป็นระบบ

ทักษะที่จะได้รับ

เมื่อจบบทความนี้ คุณจะมีความเข้าใจและทักษะที่จำเป็นสำหรับการเริ่มต้นเส้นทางนักพัฒนา React อย่างมั่นใจ:

  1. เข้าใจแก่นแนวคิด: คุณจะเข้าใจความหมายและเหตุผลเบื้องหลังของ Component, JSX, Props, State, และ Virtual DOM
  2. ตั้งค่าสภาพแวดล้อมการพัฒนา: สามารถใช้เครื่องมือสมัยใหม่อย่าง Vite ในการสร้างและรันโปรเจกต์ React ได้อย่างรวดเร็ว
  3. สร้างและประกอบคอมโพเนนต์: สามารถแบ่ง UI ที่ซับซ้อนออกเป็นคอมโพเนนต์ย่อยๆ และนำมาประกอบกันได้อย่างมีประสิทธิภาพ
  4. จัดการข้อมูลและสถานะ: รู้จักวิธีส่งข้อมูลระหว่างคอมโพเนนต์ด้วย Props และจัดการสถานะที่เปลี่ยนแปลงได้ด้วย State และ `useState` Hook
  5. เขียนเทสต์เบื้องต้น: สามารถเขียน Unit Test สำหรับคอมโพเนนต์ของคุณโดยใช้ Jest และ React Testing Library เพื่อสร้างความมั่นใจในคุณภาพของโค้ด

แก่นแนวคิดสำคัญของ React: เข้าใจก่อนลงมือเขียน

ก่อนที่เราจะเริ่มเขียนโค้ดบรรทัดแรก การทำความเข้าใจปรัชญาและแก่นแนวคิดที่ขับเคลื่อน React เป็นสิ่งสำคัญอย่างยิ่ง เพราะมันจะช่วยให้คุณตัดสินใจออกแบบสถาปัตยกรรมของแอปพลิเคชันได้อย่างถูกต้องและมีประสิทธิภาพในระยะยาว ส่วนนี้จะอธิบาย 4 แนวคิดหลักที่เปรียบเสมือนเสาหลักของ React

สถาปัตยกรรมแบบคอมโพเนนต์ (Component-Based Architecture)

แนวคิด

ลองจินตนาการว่าคุณกำลังต่อตัวต่อเลโก้เพื่อสร้างปราสาทหลังใหญ่ คุณไม่ได้สร้างปราสาททั้งหลังจากก้อนดินเหนียวก้อนเดียว แต่คุณมีตัวต่อชิ้นเล็กๆ ที่มีรูปร่างและหน้าที่แตกต่างกัน เช่น ชิ้นส่วนกำแพง, หน้าต่าง, ประตู, หรือยอดหอคอย คุณสามารถนำชิ้นส่วนเหล่านี้มาประกอบกันเพื่อสร้างโครงสร้างที่ซับซ้อนขึ้นได้

สถาปัตยกรรมแบบคอมโพเนนต์ของ React ก็ทำงานในลักษณะเดียวกัน แทนที่จะมองหน้าเว็บทั้งหน้าเป็นไฟล์ HTML ขนาดใหญ่ไฟล์เดียว React สนับสนุนให้เราแบ่งส่วนติดต่อผู้ใช้ (UI) ออกเป็นชิ้นส่วนเล็กๆ ที่ทำงานแยกจากกันและนำกลับมาใช้ใหม่ได้ ที่เรียกว่า คอมโพเนนต์ (Component)

A component is a piece of the UI (user interface) that has its own logic and appearance. A component can be as small as a button, or as large as an entire page.
- Official React Documentation

ข้อดี

ตัวอย่าง

ในหน้าเว็บหนึ่งๆ เราสามารถแบ่งส่วนต่างๆ ออกเป็นคอมโพเนนต์ได้ดังนี้:

คอมโพเนนต์เหล่านี้จะถูกจัดเรียงเป็นลำดับชั้น (Hierarchy) คล้ายโครงสร้างต้นไม้ ซึ่งสะท้อนถึงโครงสร้างของ UI ที่เราเห็นบนหน้าจอ

Virtual DOM และกระบวนการ Reconciliation

ปัญหาของ Real DOM

DOM (Document Object Model) คือโครงสร้างแบบต้นไม้ที่เบราว์เซอร์ใช้แทนหน้าเว็บ HTML การแก้ไขหรืออัปเดต DOM โดยตรงด้วย JavaScript เป็นกระบวนการที่ "แพง" และช้ามาก ทุกครั้งที่มีการเปลี่ยนแปลง แม้เพียงเล็กน้อย เช่น การเปลี่ยนสีข้อความ เบราว์เซอร์อาจจะต้องคำนวณ Layout, Style และทำการวาดหน้าจอใหม่ (Repaint/Reflow) ทั้งหมดหรือเกือบทั้งหมด ในแอปพลิเคชันสมัยใหม่ที่มีการอัปเดตข้อมูลบ่อยครั้ง (เช่น การแสดงราคาหุ้นแบบ real-time หรือฟีดข่าว) การทำเช่นนี้จะส่งผลให้ประสิทธิภาพลดลงอย่างมากและทำให้ UI กระตุก

Virtual DOM คืออะไร?

เพื่อแก้ปัญหานี้ React ได้นำเสนอแนวคิดที่เรียกว่า Virtual DOM (VDOM) ขึ้นมา Virtual DOM ไม่ใช่เทคโนโลยีของเบราว์เซอร์ แต่เป็นแนวคิดในการเขียนโปรแกรม โดยมันคือ "พิมพ์เขียว" หรือสำเนาของ Real DOM ที่ถูกเก็บไว้ในหน่วยความจำของ JavaScript ในรูปแบบของอ็อบเจกต์ธรรมดาๆ (Plain JavaScript Object) ซึ่งมีน้ำหนักเบาและทำงานได้รวดเร็วกว่าการเข้าถึง Real DOM โดยตรง

ลองนึกภาพว่า Real DOM คืออาคารจริง การจะทุบหรือต่อเติมแต่ละครั้งมีค่าใช้จ่ายสูงและใช้เวลามาก ส่วน Virtual DOM ก็เหมือนกับแบบแปลนของอาคารบนกระดาษ เราสามารถขีดเขียน แก้ไข หรือเปรียบเทียบแบบแปลนหลายๆ ฉบับได้อย่างรวดเร็วโดยไม่มีค่าใช้จ่ายใดๆ

กระบวนการทำงาน (Diffing & Reconciliation)

เมื่อมีการเปลี่ยนแปลงเกิดขึ้นในแอปพลิเคชัน React (เช่น ผู้ใช้คลิกปุ่มแล้ว State เปลี่ยน) กระบวนการที่เรียกว่า Reconciliation จะเริ่มต้นขึ้น ซึ่งมีขั้นตอนดังนี้ :

  1. สร้าง Virtual DOM ใหม่: เมื่อ State ของคอมโพเนนต์เปลี่ยนแปลง React จะเรียกใช้ฟังก์ชัน render() (หรือตัวฟังก์ชันคอมโพเนนต์เอง) อีกครั้งเพื่อสร้าง Virtual DOM tree ชุดใหม่ขึ้นมาในหน่วยความจำ
  2. เปรียบเทียบ (Diffing): React จะใช้อัลกอริทึมที่เรียกว่า "Diffing Algorithm" เพื่อเปรียบเทียบ Virtual DOM ชุดใหม่กับ Virtual DOM ชุดก่อนหน้า (ที่เก็บ snapshot ไว้) เพื่อหาว่ามีส่วนไหนที่แตกต่างกันบ้าง อัลกอริทึมนี้ถูกออกแบบมาให้ทำงานได้เร็วมาก (ความซับซ้อน O(n))
  3. อัปเดต Real DOM: หลังจากพบจุดที่แตกต่าง React จะทำการ "batch" หรือรวบรวมการเปลี่ยนแปลงทั้งหมด แล้วอัปเดตเฉพาะส่วนที่จำเป็นบน Real DOM จริงๆ เพียงครั้งเดียว การเปลี่ยนแปลงนี้จะเกิดขึ้นน้อยที่สุดเท่าที่จะเป็นไปได้
// ตัวอย่างการทำงานของ Virtual DOM
// 1. State เริ่มต้น
const ui = <div><p>Count: 0</p></div>;

// 2. State เปลี่ยนเป็น 1
const newUi = <div><p>Count: 1</p></div>;

// 3. React's Diffing Algorithm เปรียบเทียบ
//    - <div> ยังคงเป็น <div> -> ไม่ต้องทำอะไร
//    - <p> ยังคงเป็น <p> -> ไม่ต้องทำอะไร
//    - เนื้อหาข้างใน <p> เปลี่ยนจาก "Count: 0" เป็น "Count: 1" -> พบความแตกต่าง!

// 4. Reconciliation
//    - React จะอัปเดตเฉพาะ text node ภายใน <p> บน Real DOM
//    - ไม่มีการสร้าง <div> หรือ <p> ใหม่ทั้งหมด

ผลลัพธ์

ด้วยกระบวนการนี้ React สามารถลดจำนวนการเข้าถึงและแก้ไข Real DOM ซึ่งเป็นคอขวดของประสิทธิภาพลงได้อย่างมหาศาล ทำให้แอปพลิเคชัน React มีความรวดเร็วและตอบสนองได้ดี แม้จะมีการอัปเดต UI บ่อยครั้งก็ตาม

JSX: เขียน UI ใน JavaScript

นิยาม

เมื่อคุณเห็นโค้ด React ครั้งแรก สิ่งที่อาจทำให้สับสนคือ синтаксис ที่ดูคล้าย HTML แต่ถูกเขียนอยู่ในไฟล์ JavaScript สิ่งนี้เรียกว่า JSX (JavaScript XML)

สิ่งสำคัญที่ต้องเข้าใจคือ JSX ไม่ใช่ HTML และเบราว์เซอร์ก็ไม่เข้าใจ JSX โดยตรง มันเป็นเพียง Syntax Extension หรือ "น้ำตาลทางไวยากรณ์" (Syntactic Sugar) ที่ช่วยให้เราสามารถเขียน UI ที่มีโครงสร้างแบบต้นไม้ได้ง่ายและเป็นธรรมชาติมากขึ้น ก่อนที่โค้ดจะถูกรันบนเบราว์เซอร์ เครื่องมืออย่าง Babel จะทำการแปลง (Transpile) โค้ด JSX เหล่านี้ให้กลายเป็นคำสั่ง JavaScript ธรรมดาๆ ซึ่งก็คือฟังก์ชัน React.createElement() นั่นเอง

// โค้ด JSX ที่เราเขียน:
const element = <h1 className="greeting">Hello, world!</h1>;

// โค้ดที่ Babel แปลงให้ (เบื้องหลัง):
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

ข้อดี

ตัวอย่าง: การแทรก JavaScript Expression

จุดเด่นที่สุดของ JSX คือการใช้เครื่องหมายวงเล็บปีกกา {} เพื่อ "หลุด" กลับเข้าไปในโลกของ JavaScript ทำให้เราสามารถแทรกตัวแปร, ผลลัพธ์ของฟังก์ชัน, หรือ JavaScript expression ใดๆ ลงไปใน UI ได้โดยตรง

const user = {
  name: 'Hedy Lamarr',
  imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
};

function Profile() {
  return (
    <>
      <h1>{user.name}</h1>
      <img
        className="avatar"
        src={user.imageUrl}
        alt={'Photo of ' + user.name}
      />
    </>
  );
}

ในตัวอย่างนี้ {user.name} และ {user.imageUrl} จะถูกแทนที่ด้วยค่าจากอ็อบเจกต์ user ทำให้เราสามารถสร้าง UI ที่เปลี่ยนแปลงตามข้อมูลได้อย่างง่ายดาย

State และ Props: หัวใจของการขับเคลื่อนข้อมูล

หากคอมโพเนนต์คือโครงสร้างของแอปพลิเคชัน Props และ State ก็เปรียบเสมือนเลือดที่หล่อเลี้ยงและทำให้โครงสร้างนั้นมีชีวิตและเคลื่อนไหวได้ การทำความเข้าใจความแตกต่างระหว่างสองสิ่งนี้เป็นพื้นฐานที่สำคัญที่สุดในการเขียน React

Props (Properties)

// Parent Component
function App() {
  return <Greeting name="Alice" />;
}

// Child Component
function Greeting(props) {
  // รับข้อมูลผ่าน props
  // props คือ object: { name: "Alice" }
  return <h1>Hello, {props.name}!</h1>;
  
  // props.name = "Bob"; // ❌ ผิด! ไม่สามารถแก้ไข props ได้
}

State

import { useState } from 'react';

function Counter() {
  // ประกาศ state variable ชื่อ 'count' โดยมีค่าเริ่มต้นเป็น 0
  // useState คืนค่ามาเป็น array ที่มี 2 ค่า: [ค่า state ปัจจุบัน, ฟังก์ชันสำหรับอัปเดต state]
  const [count, setCount] = useState(0);

  function handleClick() {
    // เรียกใช้ฟังก์ชัน setCount เพื่ออัปเดตค่า state
    // การเรียกนี้จะทำให้ React re-render คอมโพเนนต์ Counter
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}

สรุปความแตกต่างระหว่าง Props และ State

Workshop #1: เตรียมความพร้อมและติดตั้งเครื่องมือพัฒนา

ในส่วนนี้ เราจะเปลี่ยนจากทฤษฎีมาสู่การปฏิบัติ โดยจะแนะนำคุณทีละขั้นตอนในการติดตั้งเครื่องมือที่จำเป็นทั้งหมดเพื่อสร้างสภาพแวดล้อมการพัฒนา (Development Environment) ที่พร้อมสำหรับเขียน React การมีเครื่องมือที่ดีและทันสมัยจะช่วยให้กระบวนการพัฒนาของคุณรวดเร็วและราบรื่นขึ้นอย่างมาก

1. ติดตั้ง Node.js และ Package Manager (npm/Yarn)

Node.js คืออะไร?

Node.js คือ JavaScript runtime environment ที่ทำให้เราสามารถรันโค้ด JavaScript นอกเว็บเบราว์เซอร์ได้ ในบริบทของการพัฒนา React นั้น Node.js ไม่ได้ถูกใช้เพื่อรันโค้ด React ของเราบนเซิร์ฟเวอร์โดยตรง (เว้นแต่จะทำ Server-Side Rendering) แต่มีความสำคัญในฐานะที่เป็น "หัวใจ" ของระบบนิเวศเครื่องมือพัฒนาสมัยใหม่ทั้งหมด ไม่ว่าจะเป็นตัวสร้างโปรเจกต์ (scaffolding tools), ตัวจัดการแพ็กเกจ (package managers), หรือตัวรวมโค้ด (bundlers) ล้วนทำงานบน Node.js ทั้งสิ้น

npm vs. Yarn

เมื่อคุณติดตั้ง Node.js คุณจะได้รับเครื่องมือที่ชื่อว่า npm (Node Package Manager) ติดมาด้วยโดยอัตโนมัติ npm คือเครื่องมือจัดการแพ็กเกจที่ใหญ่ที่สุดในโลก ทำหน้าที่ดาวน์โหลดและติดตั้งไลบรารีหรือ "แพ็กเกจ" ต่างๆ ที่โปรเจกต์ของเราต้องใช้ (เรียกว่า dependencies) เช่น ตัว React เอง, React DOM, หรือไลบรารีอื่นๆ

นอกจาก npm แล้ว ยังมีอีกเครื่องมือที่ได้รับความนิยมคือ Yarn ซึ่งพัฒนาโดย Facebook ในอดีต Yarn มีข้อดีเหนือ npm ในเรื่องความเร็วและความเสถียร แต่ในปัจจุบัน npm ได้รับการพัฒนาให้มีประสิทธิภาพทัดเทียมกันมากแล้ว

ข้อมูลจาก: GeeksforGeeks

คำแนะนำสำหรับผู้เริ่มต้น: เพื่อความเรียบง่าย ในบทความนี้เราจะใช้ npm เป็นหลัก เนื่องจากมันถูกติดตั้งมาพร้อมกับ Node.js อยู่แล้ว ไม่ต้องติดตั้งอะไรเพิ่มเติม

ขั้นตอนการติดตั้ง

  1. ไปที่เว็บไซต์ทางการของ Node.js: https://nodejs.org/
  2. ดาวน์โหลดตัวติดตั้งสำหรับระบบปฏิบัติการของคุณ โดยเลือกเวอร์ชัน LTS (Long-Term Support) ซึ่งเป็นเวอร์ชันที่เสถียรและแนะนำสำหรับการใช้งานส่วนใหญ่
  3. ดำเนินการติดตั้งตามขั้นตอนที่ปรากฏบนหน้าจอ (ส่วนใหญ่สามารถกด "Next" ไปได้เลย)
  4. เมื่อติดตั้งเสร็จสิ้น ให้เปิดโปรแกรม Terminal (บน macOS/Linux) หรือ Command Prompt/PowerShell (บน Windows) แล้วพิมพ์คำสั่งต่อไปนี้เพื่อตรวจสอบว่าการติดตั้งสำเร็จหรือไม่:
# ตรวจสอบเวอร์ชัน Node.js
node -v

# ตรวจสอบเวอร์ชัน npm
npm -v

หากคำสั่งทำงานและแสดงหมายเลขเวอร์ชัน (เช่น v20.11.0 และ 10.2.4) แสดงว่าคุณพร้อมสำหรับขั้นตอนต่อไปแล้ว

เกร็ดความรู้: nvm (Node Version Manager)

สำหรับนักพัฒนาที่ต้องทำงานกับหลายโปรเจกต์ ซึ่งแต่ละโปรเจกต์อาจต้องการ Node.js คนละเวอร์ชัน การติดตั้ง Node.js โดยตรงอาจไม่สะดวกนัก เครื่องมืออย่าง nvm จะเข้ามาช่วยแก้ปัญหานี้ โดยมันอนุญาตให้คุณติดตั้งและสลับเวอร์ชันของ Node.js ไปมาได้อย่างง่ายดายด้วยคำสั่งเพียงไม่กี่บรรทัด อย่างไรก็ตาม สำหรับผู้เริ่มต้น การติดตั้งเวอร์ชัน LTS เพียงเวอร์ชันเดียวก็เพียงพอแล้ว

2. สร้างโปรเจกต์ React ด้วย Vite

ทำไมต้อง Vite?

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

เหตุผลหลักที่ Vite เหนือกว่า CRA อย่างชัดเจนคือเรื่องของ "ความเร็ว" และ "ประสบการณ์การพัฒนา (Developer Experience - DX)" :

ขั้นตอนการสร้างโปรเจกต์

การสร้างโปรเจกต์ React ด้วย Vite นั้นง่ายมาก เพียงแค่ใช้คำสั่งเดียวใน Terminal:

  1. เปิด Terminal หรือ Command Prompt: นำทางไปยังโฟลเดอร์ที่คุณต้องการเก็บโปรเจกต์
  2. รันคำสั่งสร้างโปรเจกต์:
    npm create vite@latest my-react-app -- --template react
    • npm create vite@latest: เป็นคำสั่งในการเรียกใช้เครื่องมือสร้างโปรเจกต์ของ Vite เวอร์ชันล่าสุด
    • my-react-app: คือชื่อโฟลเดอร์และโปรเจกต์ของคุณ (สามารถเปลี่ยนเป็นชื่ออื่นได้)
    • -- --template react: เป็นการระบุให้ Vite ใช้เทมเพลตสำหรับ React (Vite รองรับเฟรมเวิร์กอื่นๆ ด้วย เช่น Vue, Svelte)

การรันโปรเจกต์

หลังจากคำสั่งข้างต้นทำงานเสร็จสิ้น คุณจะได้โฟลเดอร์โปรเจกต์ใหม่ตามชื่อที่ตั้งไว้ ให้ทำตามขั้นตอนต่อไปนี้:

  1. เข้าไปในโฟลเดอร์โปรเจกต์:
    cd my-react-app
  2. ติดตั้ง Dependencies: คำสั่งนี้จะอ่านไฟล์ package.json และดาวน์โหลดไลบรารีที่จำเป็นทั้งหมดมาเก็บไว้ในโฟลเดอร์ node_modules
    npm install
  3. เริ่ม Development Server:
    npm run dev

หลังจากรันคำสั่งสุดท้าย Terminal จะแสดงข้อความว่าเซิร์ฟเวอร์พร้อมทำงาน และโดยปกติแล้วเบราว์เซอร์จะเปิดหน้าเว็บขึ้นมาอัตโนมัติที่ URL http://localhost:5173 (หรือพอร์ตอื่นหากพอร์ต 5173 ไม่ว่าง) คุณจะเห็นหน้าต้อนรับของ React + Vite ซึ่งเป็นการยืนยันว่าโปรเจกต์ของคุณพร้อมสำหรับการพัฒนาแล้ว!

3. สำรวจโครงสร้างโปรเจกต์และเครื่องมือเสริม

เมื่อเปิดโปรเจกต์ที่สร้างโดย Vite ด้วย Code Editor ที่คุณชื่นชอบ (แนะนำ Visual Studio Code) คุณจะพบกับโครงสร้างไฟล์และโฟลเดอร์เบื้องต้นดังนี้:

my-react-app/
├── node_modules/      # โฟลเดอร์เก็บไลบรารีทั้งหมด (ไม่ต้องยุ่งกับส่วนนี้)
├── public/            # เก็บไฟล์สาธารณะ เช่น favicon, index.html
│   └── vite.svg
├── src/               # โฟลเดอร์หลักสำหรับซอร์สโค้ดของเรา
│   ├── assets/        # เก็บไฟล์ asset เช่น รูปภาพโลโก้
│   │   └── react.svg
│   ├── App.css        # ไฟล์ CSS สำหรับ App component
│   ├── App.jsx        # Root component ของแอปพลิเคชัน
│   ├── index.css      # ไฟล์ CSS หลัก (Global styles)
│   └── main.jsx       # จุดเริ่มต้นของแอป, ทำหน้าที่ render App component
├── .gitignore         # บอก Git ว่าไฟล์/โฟลเดอร์ไหนไม่ต้อง track
├── index.html         # ไฟล์ HTML หลักของแอป (Vite จะ inject script ให้เอง)
├── package.json       # ไฟล์ข้อมูลโปรเจกต์, dependencies, และ scripts
├── package-lock.json  # ล็อกเวอร์ชันของ dependencies
└── vite.config.js     # ไฟล์ตั้งค่าของ Vite

โครงสร้างโฟลเดอร์ที่สำคัญ

เครื่องมือแนะนำเพื่อประสบการณ์การพัฒนาที่ดีขึ้น (Recommended Tools)

การมีเครื่องมือที่เหมาะสมจะช่วยให้การเขียนโค้ดของคุณมีประสิทธิภาพและลดข้อผิดพลาดได้มาก

พื้นฐานการใช้งาน React (Workshop)

เมื่อเราตั้งค่าโปรเจกต์เรียบร้อยแล้ว ก็ถึงเวลามาทำความรู้จักกับหัวใจของการเขียน React กันจริงๆ ในส่วนนี้ เราจะเริ่มจากพื้นฐานสำหรับผู้ที่คุ้นเคยกับ HTML, CSS และ JavaScript มาแล้ว โดยจะพาทำ Workshop ง่ายๆ เพื่อให้เห็นภาพการทำงานจริง

Workshop 1: สร้าง Component แรกของคุณ

สิ่งแรกที่ต้องทำคือการ "ล้าง" โปรเจกต์เริ่มต้นให้สะอาด เพื่อให้เราเริ่มต้นจากศูนย์จริงๆ

  1. เปิดไฟล์ src/App.jsx แล้วลบโค้ดทั้งหมดออก แล้วแทนที่ด้วยโค้ดนี้:
    function App() {
      return (
        <h1>Hello, React!</h1>
      );
    }
    
    export default App;
  2. เปิดไฟล์ src/App.css และ src/index.css แล้วลบ CSS ทั้งหมดข้างในออก

เมื่อคุณบันทึกไฟล์ หน้าเว็บในเบราว์เซอร์ควรจะอัปเดตอัตโนมัติและแสดงข้อความ "Hello, React!" บนพื้นหลังสีขาว นี่คือ Component แรกของคุณ! มันคือฟังก์ชัน JavaScript ที่ return สิ่งที่ดูเหมือน HTML ซึ่งเรียกว่า JSX

Workshop 2: การใช้ JSX และการสร้าง Component ย่อย

ตอนนี้เราจะสร้าง Component ใหม่สำหรับแสดงข้อมูลผู้ใช้ และนำไปใช้ใน App component

  1. ในโฟลเดอร์ src/ สร้างโฟลเดอร์ใหม่ชื่อ components
  2. ใน src/components/ สร้างไฟล์ใหม่ชื่อ UserProfile.jsx แล้วใส่โค้ดนี้:
    function UserProfile() {
      const user = {
        name: 'Somsri',
        avatarUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
        theme: {
          backgroundColor: '#f0f8ff',
          color: '#333'
        }
      };
    
      return (
        <div style={user.theme}>
          <h2>{user.name}'s Profile</h2>
          <img 
            src={user.avatarUrl} 
            alt={'Photo of ' + user.name}
            style={{ width: 100, height: 100, borderRadius: '50%' }}
          />
        </div>
      );
    }
    
    export default UserProfile;

    สังเกตการใช้ {} เพื่อแทรกตัวแปรและนิพจน์ JavaScript ลงใน JSX และการใช้ style attribute ที่รับ JavaScript object

  3. กลับไปที่ src/App.jsx, import และใช้งาน UserProfile:
    import UserProfile from './components/UserProfile';
    
    function App() {
      return (
        <div>
          <h1>Welcome to My App</h1>
          <UserProfile />
        </div>
      );
    }
    
    export default App;

    ตอนนี้หน้าเว็บของคุณจะแสดงโปรไฟล์ของ Somsri ที่เราสร้างขึ้น

Workshop 3: การส่งข้อมูลด้วย Props

ตอนนี้ UserProfile ของเรามีข้อมูลตายตัวอยู่ข้างใน เราจะทำให้มันยืดหยุ่นขึ้นโดยการรับข้อมูลจากข้างนอกผ่าน Props

  1. แก้ไข src/components/UserProfile.jsx ให้รับ props:
    // รับ props เป็น argument ของฟังก์ชัน
    function UserProfile(props) {
      // props คือ object เช่น { user: { name: '...', ... } }
      const user = props.user;
    
      return (
        <div style={{...}}>
          <h2>{user.name}'s Profile</h2>
          <img src={user.avatarUrl} ... />
        </div>
      );
    }
    
    export default UserProfile;
  2. แก้ไข src/App.jsx เพื่อสร้างข้อมูลและส่งไปเป็น props:
    import UserProfile from './components/UserProfile';
    
    function App() {
      const somsak = {
        name: 'Somsak',
        avatarUrl: 'https://i.imgur.com/7vQD0fPs.jpg',
      };
    
      return (
        <div>
          <h1>Welcome to My App</h1>
          {/* ส่ง object 'somsak' เข้าไปใน prop ที่ชื่อ 'user' */}
          <UserProfile user={somsak} />
        </div>
      );
    }
    
    export default App;

    ตอนนี้ UserProfile สามารถแสดงข้อมูลของใครก็ได้ที่เราส่งเข้าไป ทำให้มันเป็น Component ที่นำกลับมาใช้ใหม่ได้

Workshop 4: การจัดการ State ด้วย `useState`

Props เหมาะสำหรับข้อมูลที่ส่งจากบนลงล่างและไม่เปลี่ยนแปลง แต่ถ้าเราต้องการให้ Component มี "ความจำ" และเปลี่ยนแปลงได้ตามการกระทำของผู้ใช้ เราต้องใช้ State

เราจะสร้างปุ่มนับเลขง่ายๆ

  1. ใน src/components/ สร้างไฟล์ใหม่ชื่อ Counter.jsx:
    import { useState } from 'react';
    
    function Counter() {
      // ประกาศ state variable ชื่อ 'count' และฟังก์ชันสำหรับอัปเดต 'setCount'
      // 0 คือค่าเริ่มต้นของ count
      const [count, setCount] = useState(0);
    
      function handleClick() {
        // เรียกใช้ setCount เพื่อบอก React ให้ re-render component นี้ด้วยค่า count ใหม่
        setCount(count + 1);
      }
    
      return (
        <button onClick={handleClick}>
          Clicked {count} times
        </button>
      );
    }
    
    export default Counter;
  2. นำ Counter ไปใช้ใน src/App.jsx:
    import UserProfile from './components/UserProfile';
    import Counter from './components/Counter';
    
    function App() {
      const somsak = { ... };
    
      return (
        <div>
          <h1>Welcome to My App</h1>
          <UserProfile user={somsak} />
          <hr />
          <h3>My Counter</h3>
          <Counter />
        </div>
      );
    }
    
    export default App;

    เมื่อคุณคลิกที่ปุ่ม ตัวเลขจะเพิ่มขึ้น นี่คือพลังของ State ที่ทำให้ UI ของคุณโต้ตอบได้

สรุปพื้นฐานการใช้งาน

Workshop #2: ลงมือสร้าง React Application (Thinking in React)

ตอนนี้เครื่องมือของเราพร้อมแล้ว และได้รู้จักกับพื้นฐานการสร้าง Component, Props, และ State แล้ว ก็ถึงเวลาลงมือสร้างแอปพลิเคชัน Todo List กันจริงๆ ในส่วนนี้ เราจะไม่ได้เริ่มเขียนโค้ดทันที แต่จะประยุกต์ใช้กระบวนการคิดที่เป็นระบบที่เรียกว่า "Thinking in React" ซึ่งเป็นหัวใจสำคัญที่จะช่วยให้คุณสามารถรับมือกับแอปพลิเคชันที่ซับซ้อนขึ้นในอนาคตได้

ขั้นตอนที่ 1: แบ่ง UI ออกเป็นลำดับชั้นของคอมโพเนนต์ (Component Hierarchy)

เป้าหมาย

ออกแบบหน้าตาของแอปพลิเคชัน Todo List ของเรา และแบ่งมันออกเป็นชิ้นส่วนคอมโพเนนต์เล็กๆ ที่จัดการได้ง่าย

วิธีการ

เริ่มต้นด้วยการวาดภาพ Mockup ของ UI ที่เราต้องการ จากนั้นให้วาดกล่องล้อมรอบทุกส่วนของ UI ที่ดูเหมือนจะเป็น "หน่วย" ที่แยกจากกันได้ แล้วตั้งชื่อให้มัน หลักการที่ดีในการแบ่งคือ Single Responsibility Principle: หนึ่งคอมโพเนนต์ควรมีหน้าที่รับผิดชอบเพียงอย่างเดียว ถ้ามันเริ่มทำหลายอย่างเกินไป ก็ควรแตกมันออกเป็นคอมโพเนนต์ย่อยๆ

สำหรับแอป Todo List ของเรา อาจมีหน้าตาประมาณนี้:


  Todo App
  -----------------------------------------
  [ What needs to be done?      ] [ Add ]
  -----------------------------------------
  ( ) Filter: [ All | Active | Completed ]
  -----------------------------------------
  [x] Learn React
  [ ] Build a Todo App
  [ ] Deploy to production
  -----------------------------------------
  2 items left
        

จาก Mockup นี้ เราสามารถแบ่งเป็นคอมโพเนนต์ต่างๆ ได้ดังนี้:

เมื่อได้รายชื่อคอมโพเนนต์แล้ว เราจะจัดลำดับชั้น (Hierarchy) ของมัน:

TodoApp
  ├── Header
  ├── TodoForm
  ├── FilterControls
  ├── TodoList
  │   ├── TodoItem
  │   ├── TodoItem
  │   └── ...
  └── Footer
        

การมีแผนผังนี้ในใจจะทำให้ขั้นตอนการเขียนโค้ดชัดเจนและเป็นระบบมากขึ้น

ขั้นตอนที่ 2: สร้างเวอร์ชันคงที่ (Static Version) ด้วย Props

เป้าหมาย

เขียนโค้ดเพื่อแสดงผล UI ตามที่ออกแบบไว้โดยใช้ข้อมูลจำลอง (Mock Data) โดยที่ยังไม่มีการโต้ตอบใดๆ (เช่น คลิกปุ่มแล้วยังไม่มีอะไรเกิดขึ้น) ขั้นตอนนี้เน้นไปที่การส่งข้อมูลผ่าน Props

การปฏิบัติ

  1. สร้างโฟลเดอร์และไฟล์คอมโพเนนต์: ใน src/components สร้างไฟล์สำหรับแต่ละคอมโพเนนต์ที่เราออกแบบไว้ เช่น Header.jsx, TodoForm.jsx, TodoList.jsx, TodoItem.jsx
  2. สร้างข้อมูลจำลอง (Mock Data): ในคอมโพเนนต์แม่หลัก src/App.jsx สร้าง array ของ to-do items ขึ้นมา
    // src/App.jsx
    const initialTodos = [
      { id: 1, text: 'Learn React', completed: true },
      { id: 2, text: 'Build a Todo App', completed: false },
      { id: 3, text: 'Deploy to production', completed: false },
    ];
  3. สร้างคอมโพเนนต์และส่ง Props: เริ่มสร้างคอมโพเนนต์จากบนลงล่าง (Top-down) หรือล่างขึ้นบน (Bottom-up) ก็ได้ ในที่นี้เราจะเริ่มจาก App.jsx แล้วส่งข้อมูล initialTodos ลงไปให้ TodoList ผ่าน props
    // src/App.jsx
    import TodoList from './components/TodoList';
    
    function App() {
      const initialTodos = [ ... ]; // ข้อมูลจากข้อ 2
    
      return (
        <div>
          {/* ... คอมโพเนนต์อื่นๆ ... */}
          <TodoList todos={initialTodos} />
        </div>
      );
    }
  4. คอมโพเนนต์ลูกรับ Props มาแสดงผล: ใน TodoList.jsx จะรับ todos มาจาก props แล้วใช้ฟังก์ชัน .map() เพื่อวนลูปสร้าง TodoItem สำหรับแต่ละรายการ
    // src/components/TodoList.jsx
    import TodoItem from './TodoItem';
    
    function TodoList({ todos }) { // Destructuring props
      return (
        <ul>
          {todos.map(todo => (
            <TodoItem key={todo.id} todo={todo} />
          ))}
        </ul>
      );
    }
    export default TodoList;
    ข้อควรจำ: เมื่อทำการเรนเดอร์ลิสต์ใน React เราจำเป็นต้องใส่ key prop ที่มีค่าไม่ซ้ำกัน (unique) ให้กับ element หลักของแต่ละ item (ในที่นี้คือ <TodoItem>) React ใช้ key นี้เพื่อติดตามว่า item ไหนมีการเปลี่ยนแปลง, เพิ่ม, หรือลบไป เพื่ออัปเดต DOM ได้อย่างมีประสิทธิภาพ
  5. สร้าง TodoItem.jsx ให้สมบูรณ์:
    // src/components/TodoItem.jsx
    function TodoItem({ todo }) {
      return (
        <li style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
          {todo.text}
        </li>
      );
    }
    export default TodoItem;

ณ จุดนี้ แอปพลิเคชันของคุณควรจะแสดงรายการ to-do ทั้งหมดตามข้อมูลจำลองได้แล้ว แต่ยังไม่สามารถโต้ตอบใดๆ ได้ ซึ่งเป็นไปตามเป้าหมายของขั้นตอนนี้

สามารถใช้ Bootstrap หรือติดตั้งไลบรารี CSS อื่นๆ เพื่อทำให้ UI สวยงามขึ้นได้ตามต้องการได้ วิธีง่าย ๆ คือ การใช้ CDN link ใน public/index.html และเพิ่มคำสั่งต่อไปนี้

  <link
    href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
    rel="stylesheet"
  />

แต่ถ้าต้องการติดตั้งผ่าน npm ก็สามารถ แนะนำทำการติดตั้ง Bootstrap ใน React project ของคุณ โดยรันคำสั่งนี้

  npm install bootstrap

และใช้คำสั่ง import เพื่อนำไปใช้ เช่น ใส่ในไฟล์ src\main.jsx

  import 'bootstrap/dist/css/bootstrap.min.css';

ขั้นตอนที่ 3: หา State ที่จำเป็นและน้อยที่สุด

เป้าหมาย

ระบุว่าข้อมูลส่วนไหนในแอปพลิเคชันที่ต้อง "เปลี่ยนแปลงได้" และ "ถูกจดจำ" เพื่อให้ UI สามารถโต้ตอบกับผู้ใช้ได้

หลักการ "DRY" (Don't Repeat Yourself) สำหรับ State

หัวใจสำคัญของการจัดการ State ที่ดีคือการเก็บ State ให้น้อยที่สุดเท่าที่จำเป็น ถามตัวเองด้วยคำถามเหล่านี้สำหรับข้อมูลแต่ละชิ้น:

  1. มันถูกส่งมาจาก Parent ผ่าน Props หรือไม่? ถ้าใช่, มัน ไม่ใช่ State
  2. มันไม่เคยเปลี่ยนแปลงตลอดการใช้งานใช่หรือไม่? ถ้าใช่, มัน ไม่ใช่ State (อาจเป็นค่าคงที่)
  3. คุณสามารถคำนวณมันจาก Props หรือ State อื่นๆ ที่มีอยู่แล้วได้หรือไม่? ถ้าใช่, มัน ไม่ใช่ State อย่างแน่นอน!

สิ่งที่เหลืออยู่หลังจากผ่านคำถามเหล่านี้ คือสิ่งที่ควรจะเป็น State

ตัวอย่างใน Todo App

สรุปแล้ว State ที่จำเป็นสำหรับแอปของเราคือ: 1) รายการ to-do ทั้งหมด, 2) ข้อความใน input, และ 3) ค่า filter ปัจจุบัน

ขั้นตอนที่ 4: ระบุตำแหน่งที่ State ควรจะอยู่ (Lifting State Up)

เป้าหมาย

ตัดสินใจว่าคอมโพเนนต์ใดควรเป็น "เจ้าของ" (Owner) ของ State แต่ละตัวที่เราหามาได้ในขั้นตอนที่แล้ว

หลักการ

React ใช้การไหลของข้อมูลทิศทางเดียว (one-way data flow) ดังนั้นการหาบ้านที่เหมาะสมให้ State จึงสำคัญมาก เราใช้เทคนิคที่เรียกว่า "Lifting State Up" ซึ่งมีขั้นตอนดังนี้:

  1. สำหรับ State แต่ละตัว, ให้หาคอมโพเนนต์ ทั้งหมด ที่ต้องใช้ข้อมูลจาก State นั้นเพื่อแสดงผล
  2. หา "คอมโพเนนต์แม่ร่วมที่ใกล้ที่สุด" (Closest Common Parent) ของคอมโพเนนต์เหล่านั้นในลำดับชั้น
  3. ย้าย State ไปไว้ที่คอมโพเนนต์แม่ร่วมนั้น

การประยุกต์ใช้ใน Todo App

ตอนนี้เราจะแก้ไข App.jsx เพื่อประกาศ State โดยใช้ useState Hook:

// src/App.jsx
import { useState } from 'react';
import TodoList from './components/TodoList';
// ... import คอมโพเนนต์อื่นๆ

function App() {
  const initialTodos = [ ... ];
  
  // ย้ายข้อมูลจำลองมาเป็นค่าเริ่มต้นของ state
  const [todos, setTodos] = useState(initialTodos);
  const [filter, setFilter] = useState('All'); // 'All', 'Active', 'Completed'

  // คำนวณ filteredTodos (ไม่ใช่ state!)
  const filteredTodos = todos.filter(todo => {
    if (filter === 'Active') return !todo.completed;
    if (filter === 'Completed') return todo.completed;
    return true; // 'All'
  });

  return (
    <div>
      {/* ... */}
      <TodoList todos={filteredTodos} />
      {/* ... */}
    </div>
  );
}

ขั้นตอนที่ 5: เพิ่มการไหลของข้อมูลย้อนกลับ (Inverse Data Flow)

เป้าหมาย

ทำให้คอมโพเนนต์ลูกสามารถอัปเดต State ที่อยู่ในคอมโพเนนต์แม่ได้ เพื่อให้แอปพลิเคชันโต้ตอบกับผู้ใช้ได้อย่างสมบูรณ์

วิธีการ

เนื่องจากคอมโพเนนต์ลูกไม่สามารถแก้ไข State ของแม่ได้โดยตรง วิธีการคือ แม่จะต้องส่ง "ฟังก์ชันสำหรับอัปเดต State" ลงไปให้ลูกผ่านทาง Props

  1. สร้างฟังก์ชันอัปเดต State ในคอมโพเนนต์แม่: ใน App.jsx เราจะสร้างฟังก์ชันสำหรับเพิ่ม to-do ใหม่
    // src/App.jsx
    function App() {
      const [todos, setTodos] = useState(...);
      const [newTodoText, setNewTodoText] = useState(''); // State สำหรับ input
    
      const handleAddTodo = (text) => {
        const newTodo = {
          id: Date.now(), // ใช้วิธีง่ายๆ ในการสร้าง id ที่ไม่ซ้ำกัน
          text: text,
          completed: false,
        };
        setTodos([...todos, newTodo]); // เพิ่ม to-do ใหม่เข้าไปใน array
      };
    
      // ...
    }
  2. ส่งฟังก์ชันลงไปเป็น Prop: ส่งฟังก์ชัน handleAddTodo และ State ที่เกี่ยวข้องลงไปให้ TodoForm
    // ใน return ของ App.jsx
    <TodoForm 
      newTodoText={newTodoText}
      onNewTodoTextChange={setNewTodoText}
      onAddTodo={handleAddTodo} 
    />
  3. เรียกใช้ฟังก์ชันจากคอมโพเนนต์ลูก: ใน TodoForm.jsx จะรับฟังก์ชันเหล่านี้มาผ่าน props และเรียกใช้เมื่อมี event เกิดขึ้น
    // src/components/TodoForm.jsx
    function TodoForm({ newTodoText, onNewTodoTextChange, onAddTodo }) {
    
      const handleSubmit = (e) => {
        e.preventDefault(); // ป้องกันการรีเฟรชหน้า
        if (newTodoText.trim() === '') return; // ไม่เพิ่มถ้า input ว่าง
        onAddTodo(newTodoText);
        onNewTodoTextChange(''); // เคลียร์ input field หลังเพิ่ม
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input 
            type="text"
            value={newTodoText}
            onChange={(e) => onNewTodoTextChange(e.target.value)}
            placeholder="What needs to be done?"
          />
          <button type="submit">Add</button>
        </form>
      );
    }
    export default TodoForm;

ด้วยเทคนิคนี้ เราได้สร้าง "การไหลของข้อมูลย้อนกลับ" (Inverse Data Flow) ได้สำเร็จ เมื่อผู้ใช้พิมพ์ใน TodoForm และกด Add, TodoForm จะเรียกฟังก์ชัน onAddTodo ที่ได้รับมาจาก App ซึ่งจะไปอัปเดต State todos ใน App เมื่อ State todos เปลี่ยน, App และคอมโพเนนต์ลูกอย่าง TodoList จะ re-render เพื่อแสดงผลรายการใหม่ ทำให้แอปพลิเคชันของเราทำงานได้อย่างสมบูรณ์!

การใช้ Font Awesome ใน React สามารถทำได้โดยการติดตั้งไลบรารีผ่าน npm หรือใช้ CDN link ก็ได้ แนะนำให้ติดตั้งผ่าน npm เพื่อความสะดวกในการจัดการเวอร์ชันและการใช้งานในโปรเจกต์ของคุณ โดยรันคำสั่งนี้

npm install @fortawesome/fontawesome-svg-core
npm install @fortawesome/free-solid-svg-icons
npm install @fortawesome/react-fontawesome

หลังจากติดตั้งเสร็จแล้ว คุณสามารถนำไอคอนมาใช้ในคอมโพเนนต์ของคุณได้ดังนี้:

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlus } from '@fortawesome/free-solid-svg-icons'; // นำเข้าไอคอนที่ต้องการ  

สมมุติถ้าอยากจะนำไอคอนมาใช้ในปุ่ม Add ใน TodoForm.jsx คุณสามารถทำได้ดังนี้ (เอกสารประกอบจะแยกเป็นบทความต่างหาก)

<button type="submit">
  <FontAwesomeIcon icon={faPlus} /> Add
</button>

Workshop #3: การทดสอบคอมโพเนนต์ (Component Testing)

การเขียนโค้ดที่ทำงานได้เป็นเพียงครึ่งหนึ่งของงานพัฒนามืออาชีพ อีกครึ่งที่สำคัญไม่แพ้กันคือการเขียนเทสต์ (Test) เพื่อให้มั่นใจว่าโค้ดของเราทำงานได้ถูกต้องตามที่คาดหวัง, ไม่พังเมื่อมีการแก้ไขในอนาคต (Regression), และทำให้เรากล้าที่จะ Refactor โค้ดให้ดีขึ้น ในส่วนนี้ เราจะมาเรียนรู้พื้นฐานการทดสอบคอมโพเนนต์ React กัน

1. รู้จักกับ Testing Pyramid และเครื่องมือ

Testing Pyramid

เป็นแนวคิดที่ช่วยจัดลำดับความสำคัญของการเขียนเทสต์ แบ่งออกเป็น 3 ระดับหลักๆ :

ใน Workshop นี้ เราจะเน้นไปที่ Unit Tests และ Integration Tests สำหรับคอมโพเนนต์ ซึ่งเป็นจุดที่ให้ผลตอบแทนคุ้มค่าที่สุดสำหรับนักพัฒนา React

เครื่องมือหลัก

ในระบบนิเวศของ React เครื่องมือที่เราจะใช้เป็นหลักคือ:

ข่าวดีคือ โปรเจกต์ที่สร้างด้วย Vite จะมีการตั้งค่า Jest (หรือ Vitest ซึ่งเป็นทางเลือกที่เข้ากันได้) และ React Testing Library มาให้เบื้องต้นแล้ว

2. การเขียน Unit Test แรกของคุณ

เป้าหมาย

ทดสอบคอมโพเนนต์ง่ายๆ ที่ไม่มี State และไม่มีการโต้ตอบ เช่น Header เพื่อให้แน่ใจว่ามันแสดงผลข้อความที่ถูกต้อง

หลักการ Arrange-Act-Assert (AAA)

เป็นโครงสร้างการเขียนเทสต์ที่ดีและอ่านง่าย :

ตัวอย่างโค้ด (`Header.test.jsx`)

สร้างไฟล์เทสต์สำหรับคอมโพเนนต์ Header โดยใช้ชื่อ Header.test.jsx วางไว้ในโฟลเดอร์เดียวกับตัวคอมโพเนนต์หรือในโฟลเดอร์ __tests__

// src/components/Header.test.jsx
import { render, screen } from '@testing-library/react';
import { describe, test, expect } from 'vitest'; // Vite ใช้ Vitest ซึ่ง API คล้าย Jest
import Header from './Header';

// describe ใช้สำหรับจัดกลุ่มเทสต์ที่เกี่ยวข้องกัน
describe('Header Component', () => {

  // test หรือ it คือ test case แต่ละอัน
  test('should render the application title correctly', () => {
    // 1. Arrange: เรนเดอร์คอมโพเนนต์ Header
    render(<Header />);

    // 2. Act: (ไม่มีในเทสต์นี้)

    // 3. Assert: ตรวจสอบผลลัพธ์
    // screen.getByText() คือ query function ของ RTL เพื่อค้นหา element จากข้อความที่มองเห็น
    // เราใช้ Regular Expression /Todo App/i เพื่อให้ไม่สน case-sensitive
    const titleElement = screen.getByText(/Todo App/i);

    // expect(...).toBeInTheDocument() คือ assertion เพื่อยืนยันว่า element นั้นอยู่บนหน้าจอ
    expect(titleElement).toBeInTheDocument();
  });

});

ในการรันเทสต์ ให้เปิด Terminal แล้วใช้คำสั่ง:

npm test

คุณควรจะเห็นผลลัพธ์ว่าเทสต์ผ่าน ซึ่งเป็นการยืนยันว่าคอมโพเนนต์ Header ของเราทำงานได้ตามที่คาดหวัง

3. การทดสอบการโต้ตอบและ State (Interaction & State Testing)

เป้าหมาย

ทดสอบคอมโพเนนต์ที่มีการโต้ตอบกับผู้ใช้และมีการเปลี่ยนแปลง State เช่น คอมโพเนนต์ Counter ที่เราสร้างไว้ก่อนหน้านี้

การปฏิบัติ

เราจะใช้ user-event เพื่อจำลองการคลิกของผู้ใช้ และตรวจสอบว่า UI อัปเดตตาม State ที่เปลี่ยนไปอย่างถูกต้อง

ตัวอย่างโค้ด (`Counter.test.jsx`)

// src/components/Counter.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, test, expect } from 'vitest';
import Counter from './Counter';

describe('Counter Component', () => {
  test('should render initial count of 0', () => {
    // Arrange
    render(<Counter />);
    
    // Assert
    const countElement = screen.getByText(/Clicked 0 times/i);
    expect(countElement).toBeInTheDocument();
  });

  test('should increment count after one click', async () => {
    // Arrange
    const user = userEvent.setup(); // ตั้งค่า user-event
    render(<Counter />);
    const button = screen.getByRole('button', { name: /Click me/i });

    // Act
    await user.click(button);

    // Assert
    const countElement = screen.getByText(/Clicked 1 time/i); // ข้อความอาจเปลี่ยนไป
    expect(countElement).toBeInTheDocument();
  });

  test('should increment count after multiple clicks', async () => {
    // Arrange
    const user = userEvent.setup();
    render(<Counter />);
    const button = screen.getByRole('button', { name: /Click me/i });

    // Act
    await user.click(button);
    await user.click(button);
    await user.click(button);

    // Assert
    const countElement = screen.getByText(/Clicked 3 times/i);
    expect(countElement).toBeInTheDocument();
  });
});
Querying Best Practices: สังเกตว่าเราใช้ screen.getByRole('button', { name: /Click me/i }) เพื่อหาปุ่ม ซึ่งเป็นวิธีที่ RTL แนะนำ เพราะมันใกล้เคียงกับวิธีที่ผู้ใช้ (โดยเฉพาะผู้ใช้ screen reader) หา element บนหน้าจอมากที่สุด

4. การจำลองข้อมูล (Mocking)

ทำไมต้อง Mock?

ในแอปพลิเคชันจริง คอมโพเนนต์ของเรามักจะต้องพึ่งพาสิ่งภายนอก เช่น การเรียก API จาก Backend, การใช้ฟังก์ชันจากไลบรารีอื่น, หรือการรับฟังก์ชันผ่าน Props ในการทำ Unit/Integration Test เราต้องการทดสอบ "ตรรกะของคอมโพเนนต์เราเอง" โดยแยกตัวออกจากความไม่แน่นอนของระบบภายนอก (เช่น Backend อาจจะล่ม) การ Mocking คือเทคนิคในการสร้าง "ตัวปลอม" ของ dependencies เหล่านั้นขึ้นมา เพื่อให้เราควบคุมผลลัพธ์ของมันได้ในการทดสอบ

ตัวอย่าง: การ Mock ฟังก์ชันที่ส่งผ่าน Props

สมมติเรามีคอมโพเนนต์ ButtonWrapper ที่รับฟังก์ชัน onClick มาเป็น prop เราต้องการทดสอบว่าเมื่อผู้ใช้คลิกปุ่ม ฟังก์ชัน onClick นั้นถูกเรียกใช้จริงหรือไม่

// ButtonWrapper.jsx
function ButtonWrapper({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}

// ButtonWrapper.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, test, expect, vi } from 'vitest'; // vi คือ mock utility ของ Vitest, เทียบเท่า jest
import ButtonWrapper from './ButtonWrapper';

describe('ButtonWrapper', () => {
  test('should call onClick prop when clicked', async () => {
    // Arrange
    const user = userEvent.setup();
    const handleClickMock = vi.fn(); // สร้าง mock function ด้วย Vitest (เทียบเท่า jest.fn())
    render(<ButtonWrapper onClick={handleClickMock}>Click Me</ButtonWrapper>);
    const button = screen.getByRole('button', { name: /click me/i });

    // Act
    await user.click(button);

    // Assert
    // ตรวจสอบว่า mock function ของเราถูกเรียกใช้
    expect(handleClickMock).toHaveBeenCalled();
    // ตรวจสอบให้ละเอียดขึ้นว่าถูกเรียกใช้กี่ครั้ง
    expect(handleClickMock).toHaveBeenCalledTimes(1);
  });
});

ในตัวอย่างนี้ vi.fn() (หรือ jest.fn()) จะสร้างฟังก์ชันจำลองที่คอยบันทึกการเรียกใช้งานทั้งหมด ทำให้เราสามารถตรวจสอบได้ว่ามันถูกเรียกหรือไม่, ถูกเรียกกี่ครั้ง, หรือถูกเรียกด้วย arguments อะไรบ้าง ซึ่งเป็นเครื่องมือที่ทรงพลังอย่างยิ่งในการทดสอบการทำงานร่วมกันของคอมโพเนนต์

สรุปและขั้นตอนถัดไป

ตลอดบทความและ Workshop นี้ เราได้เดินทางผ่านเส้นทางการเรียนรู้ React ตั้งแต่การทำความเข้าใจแนวคิดหลักที่ทำให้มันทรงพลัง, การตั้งค่าสภาพแวดล้อมการพัฒนาที่ทันสมัยด้วย Vite, การลงมือสร้างแอปพลิเคชัน Todo List ผ่านกระบวนการคิดที่เป็นระบบ, และปิดท้ายด้วยการเขียนเทสต์เพื่อสร้างความมั่นใจในคุณภาพของโค้ด

สรุปสิ่งที่เราได้เรียนรู้และสร้าง

แนวทางการศึกษาต่อ (What's Next?)

เส้นทางของ React ยังมีอะไรให้เรียนรู้อีกมากมาย นี่คือหัวข้อและเครื่องมือที่คุณควรศึกษาต่อเพื่อต่อยอดความรู้ของคุณ:

ขอให้สนุกกับการเดินทางในโลกของ React! การเรียนรู้และฝึกฝนอย่างสม่ำเสมอคือหนทางสู่การเป็นนักพัฒนาที่ยอดเยี่ยม

ภาคผนวก: คู่มือสรุปฉบับปฏิบัติ (Cheatsheet)

ส่วนนี้คือสรุปขั้นตอนการปฏิบัติทั้งหมดในรูปแบบตารางและรายการตรวจสอบ เพื่อให้คุณสามารถกลับมาทบทวนและใช้เป็นแนวทางในการสร้างโปรเจกต์ของคุณเองได้อย่างรวดเร็ว

เป้าหมายและผลลัพธ์ของโปรเจกต์

สิ่งที่ต้องเตรียม (Prerequisites)

หมวดหมู่ รายการ เวอร์ชันที่แนะนำ วิธีตรวจสอบ
ฮาร์ดแวร์ ระบบปฏิบัติการ Windows, macOS, หรือ Linux -
ซอฟต์แวร์ Node.js v18.x.x หรือสูงกว่า (LTS) node -v
npm v9.x.x หรือสูงกว่า npm -v
ความรู้ JavaScript (ES6+) เข้าใจพื้นฐาน (ตัวแปร, ฟังก์ชัน, array, object) -
HTML/CSS เข้าใจการสร้างโครงสร้างและตกแต่งเว็บเบื้องต้น -

คู่มือการลงมือทำทีละขั้นตอน (Step-by-Step Guide)

ช่วงที่ 1: แนวคิดและตั้งค่าสภาพแวดล้อม (ประมาณ 30 นาที)

  1. ขั้นตอนที่ 1.1: ทบทวนแนวคิดหลัก

    อ่านและทำความเข้าใจแนวคิด: Component, Props, State, และ Virtual DOM จากส่วนต้นของบทความ

  2. ขั้นตอนที่ 1.2: ติดตั้ง Node.js

    ดาวน์โหลดและติดตั้งเวอร์ชัน LTS จาก nodejs.org
    ตรวจสอบการติดตั้งด้วยคำสั่ง:

    node -v
    npm -v
  3. ขั้นตอนที่ 1.3: สร้างโปรเจกต์ด้วย Vite

    เปิด Terminal และรันคำสั่ง:

    npm create vite@latest my-react-app -- --template react

    เข้าไปยังโฟลเดอร์โปรเจกต์และติดตั้ง dependencies:

    cd my-react-app
    npm install

    เริ่ม Development Server (คาดว่าจะเห็นหน้าต้อนรับที่ http://localhost:5173):

    npm run dev

ช่วงที่ 2: Workshop การสร้างคอมโพเนนต์และจัดการ State (ประมาณ 1.5 ชั่วโมง)

  1. Workshop 1: สร้างคอมโพเนนต์แรก (`Header`)

    ภารกิจ: สร้าง Header component เพื่อแสดงชื่อแอปพลิเคชัน

    การปฏิบัติ:

    1. ใน src/ สร้างโฟลเดอร์ components และสร้างไฟล์ Header.jsx
    2. เขียนโค้ด:
      function Header() {
        return <h1>My Todo List</h1>;
      }
      export default Header;
    3. ใน src/App.jsx ลบโค้ดเริ่มต้นออก และนำเข้า Header มาใช้งาน:
      import Header from './components/Header';
      
      function App() {
        return (
          <div>
            <Header />
          </div>
        );
      }
      export default App;

    การตรวจสอบ: รีเฟรชเบราว์เซอร์ ควรเห็นข้อความ "My Todo List"

  2. Workshop 2: จัดการ State ด้วย `useState` (`Counter` ตัวอย่าง)

    ภารกิจ: สร้างปุ่มนับเลขที่เมื่อคลิกแล้วตัวเลขจะเพิ่มขึ้น

    การปฏิบัติ:

    1. สร้างไฟล์ src/components/Counter.jsx
    2. ใช้ useState Hook เพื่อสร้าง state count
    3. เขียนฟังก์ชัน handleClick เพื่อเพิ่มค่า count
    4. แสดงผลค่า count และปุ่มสำหรับคลิก:
      import { useState } from 'react';
      
      function Counter() {
        const [count, setCount] = useState(0);
      
        const handleClick = () => {
          setCount(count + 1);
        };
      
        return (
          <div>
            <p>You clicked {count} times</p>
            <button onClick={handleClick}>Click me</button>
          </div>
        );
      }
      export default Counter;
    5. นำ Counter ไปใช้ใน App.jsx

    การตรวจสอบ: คลิกที่ปุ่ม แล้วตัวเลขบนหน้าจอควรจะเพิ่มขึ้น

ช่วงที่ 3: การทดสอบและ Build (ประมาณ 30 นาที)

  1. ขั้นตอนที่ 3.1: เขียน Unit Test สำหรับ `Header`

    ภารกิจ: เขียนเทสต์เพื่อยืนยันว่า Header แสดงผลข้อความถูกต้อง

    การปฏิบัติ:

    1. สร้างไฟล์ src/components/Header.test.jsx
    2. เขียนโค้ดทดสอบ:
      import { render, screen } from '@testing-library/react';
      import Header from './Header';
      import { test, expect } from 'vitest';
      
      test('renders headline', () => {
        render(<Header />);
        const headline = screen.getByText(/My Todo List/i);
        expect(headline).toBeInTheDocument();
      });
  2. ขั้นตอนที่ 3.2: รันเทสต์

    ใน Terminal รันคำสั่ง:

    npm test

    การตรวจสอบ: Terminal ควรแสดงผลว่าเทสต์ทั้งหมดผ่าน

  3. ขั้นตอนที่ 3.3: Build โปรเจกต์สำหรับ Production

    รันคำสั่ง Build:

    npm run build

    การตรวจสอบ: จะมีโฟลเดอร์ dist ถูกสร้างขึ้นในโปรเจกต์

รายการตรวจสอบสุดท้าย (Final Checklist)

คำถามที่พบบ่อยและวิธีแก้ปัญหา (FAQ)

ปัญหา สาเหตุที่เป็นไปได้ แนวทางแก้ไข
คำสั่ง npm ไม่ทำงาน ยังไม่ได้ติดตั้ง Node.js หรือ PATH ไม่ถูกต้อง ติดตั้ง Node.js ใหม่ หรือ Restart Terminal/Computer
พอร์ต 5173 ถูกใช้งานแล้ว มีโปรเจกต์ Vite อื่นกำลังรันอยู่ ปิด Terminal ของโปรเจกต์เก่า หรือรัน npm run dev -- --port 3001 เพื่อเปลี่ยนพอร์ต
ติดตั้ง dependencies ไม่สำเร็จ ปัญหาเครือข่าย หรือ npm registry ตรวจสอบการเชื่อมต่ออินเทอร์เน็ต หรือลองรัน npm cache clean --force แล้วลอง npm install อีกครั้ง

Reference

[1]
สร้างแอป React ตัวแรกของคุณตั้งแต่เริ่มต้นในปี 2025
https://ecommercefastlane.com/th/create-your-first-react-app-from-scratch/
[2]
ทำความรู้จัก Yarn สำหรับจัดการ dependency ของ JavaScript
https://www.somkiat.cc/hello-yarn/
[3]
จัดการกับ Version ของ Node ด้วย NVM
https://codinggun.com/javascript/nvm/
[4]
สร้างโปรเจ็ค React ด้วยการใช้ Vite
https://www.devahoy.com/blog/create-react-project-with-vite/
[7]
Angular vs React vs Vue: Core Differences
https://www.browserstack.com/guide/angular-vs-react-vs-vue
[9]
Understanding Virtual DOM in React
https://refine.dev/blog/react-virtual-dom/
[12]
[13]
[17]
React State Management in 2025: What You Actually Need
https://www.developerway.com/posts/react-state-management-2025
[19]
[22]
State of JavaScript 2024: Libraries
https://2024.stateofjs.com/en-US/libraries/