ก่อนจะเจาะลึกเรื่อง React เรามาทำความเข้าใจคำสองคำที่มักจะได้ยินบ่อยๆ ในโลกของการพัฒนาเว็บนั่นคือ Framework และ Library การเข้าใจความแตกต่างนี้จะช่วยให้เห็นภาพว่า React อยู่ตรงไหนในระบบนิเวศของการพัฒนา
ลองจินตนาการว่าคุณกำลังสร้างบ้าน
สรุปคือ React เป็น Library ที่มีความยืดหยุ่นสูง ให้อิสระแก่นักพัฒนาในการเลือกเครื่องมืออื่นๆ มาประกอบร่างเป็นแอปพลิเคชัน ในขณะที่ Framework จะมอบโครงสร้างและเครื่องมือที่ครบครันมาให้ตั้งแต่ต้น แต่ก็ต้องแลกมากับความยืดหยุ่นที่น้อยกว่า
ในยุคที่เว็บแอปพลิเคชันมีความซับซ้อนและต้องการประสบการณ์ผู้ใช้ (User Experience) ที่รวดเร็วและโต้ตอบได้ทันที การเลือกเครื่องมือที่เหมาะสมจึงเป็นกุญแจสำคัญสู่ความสำเร็จ React ซึ่งเป็นไลบรารี JavaScript ที่สร้างและดูแลโดย Meta (Facebook) ได้ก้าวขึ้นมาเป็นหนึ่งในเครื่องมือแถวหน้าสำหรับนักพัฒนา Frontend ทั่วโลก บทความนี้จะพาคุณดำดิ่งสู่โลกของ React ตั้งแต่แนวคิดพื้นฐานที่ทำให้มันทรงพลัง ไปจนถึงการลงมือสร้างแอปพลิเคชันจริงผ่าน Workshop ที่ทำตามได้ง่าย และปิดท้ายด้วยการเขียนเทสต์เพื่อรับประกันคุณภาพของโค้ด
เหตุผลที่ React ได้รับความนิยมอย่างล้นหลามไม่ได้เกิดขึ้นโดยบังเอิญ แต่มาจากปรัชญาการออกแบบและระบบนิเวศ (Ecosystem) ที่แข็งแกร่งซึ่งตอบโจทย์การพัฒนาสมัยใหม่ได้อย่างลงตัว:
ในอดีต jQuery เคยเป็นเครื่องมือสำคัญที่ช่วยให้การจัดการ DOM และการสร้าง Interaction บนเว็บทำได้ง่ายขึ้น อย่างไรก็ตาม เมื่อแอปพลิเคชันมีความซับซ้อนมากขึ้น การจัดการ UI ด้วย jQuery เริ่มแสดงให้เห็นข้อจำกัด โดยเฉพาะการที่ต้องเขียนโค้ดเพื่อ "สั่ง" ให้ DOM เปลี่ยนแปลงทีละขั้นตอน (Imperative) ซึ่งทำให้โค้ดซับซ้อนและดูแลรักษายาก
React แก้ปัญหานี้ด้วยแนวทางที่แตกต่างอย่างสิ้นเชิง:
ทฤษฎีเพียงอย่างเดียวอาจไม่เห็นภาพ เพื่อให้คุณเข้าใจการทำงานของ React อย่างแท้จริง เราจะลงมือสร้างแอปพลิเคชัน "รายการสิ่งที่ต้องทำ" (Todo List) ไปพร้อมกันทีละขั้นตอน แอปพลิเคชันนี้แม้จะดูเรียบง่าย แต่ครอบคลุมแนวคิดหลักที่สำคัญของ React ไว้อย่างครบถ้วน:
Workshop นี้จะนำคุณผ่านกระบวนการคิดแบบ "Thinking in React" ซึ่งเป็นทักษะสำคัญที่จะช่วยให้คุณสามารถออกแบบและสร้างแอปพลิเคชัน React อื่นๆ ในอนาคตได้อย่างเป็นระบบ
เมื่อจบบทความนี้ คุณจะมีความเข้าใจและทักษะที่จำเป็นสำหรับการเริ่มต้นเส้นทางนักพัฒนา React อย่างมั่นใจ:
ก่อนที่เราจะเริ่มเขียนโค้ดบรรทัดแรก การทำความเข้าใจปรัชญาและแก่นแนวคิดที่ขับเคลื่อน React เป็นสิ่งสำคัญอย่างยิ่ง เพราะมันจะช่วยให้คุณตัดสินใจออกแบบสถาปัตยกรรมของแอปพลิเคชันได้อย่างถูกต้องและมีประสิทธิภาพในระยะยาว ส่วนนี้จะอธิบาย 4 แนวคิดหลักที่เปรียบเสมือนเสาหลักของ React
ลองจินตนาการว่าคุณกำลังต่อตัวต่อเลโก้เพื่อสร้างปราสาทหลังใหญ่ คุณไม่ได้สร้างปราสาททั้งหลังจากก้อนดินเหนียวก้อนเดียว แต่คุณมีตัวต่อชิ้นเล็กๆ ที่มีรูปร่างและหน้าที่แตกต่างกัน เช่น ชิ้นส่วนกำแพง, หน้าต่าง, ประตู, หรือยอดหอคอย คุณสามารถนำชิ้นส่วนเหล่านี้มาประกอบกันเพื่อสร้างโครงสร้างที่ซับซ้อนขึ้นได้
สถาปัตยกรรมแบบคอมโพเนนต์ของ 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
Button หรือ Card เพียงครั้งเดียว แล้วนำไปใช้ซ้ำได้ทั่วทั้งแอปพลิเคชัน หรือแม้กระทั่งในโปรเจกต์อื่นๆ ซึ่งช่วยลดการเขียนโค้ดซ้ำซ้อนได้อย่างมหาศาลในหน้าเว็บหนึ่งๆ เราสามารถแบ่งส่วนต่างๆ ออกเป็นคอมโพเนนต์ได้ดังนี้:
Header: ส่วนหัวของเว็บที่อาจมีโลโก้และเมนูนำทางSidebar: แถบด้านข้างสำหรับแสดงหมวดหมู่ArticleList: คอมโพเนนต์ที่แสดงรายการบทความArticleItem: คอมโพเนนต์ย่อยภายใน ArticleList ที่แสดงบทความแต่ละชิ้นFooter: ส่วนท้ายของเว็บไซต์คอมโพเนนต์เหล่านี้จะถูกจัดเรียงเป็นลำดับชั้น (Hierarchy) คล้ายโครงสร้างต้นไม้ ซึ่งสะท้อนถึงโครงสร้างของ UI ที่เราเห็นบนหน้าจอ
DOM (Document Object Model) คือโครงสร้างแบบต้นไม้ที่เบราว์เซอร์ใช้แทนหน้าเว็บ HTML การแก้ไขหรืออัปเดต DOM โดยตรงด้วย JavaScript เป็นกระบวนการที่ "แพง" และช้ามาก ทุกครั้งที่มีการเปลี่ยนแปลง แม้เพียงเล็กน้อย เช่น การเปลี่ยนสีข้อความ เบราว์เซอร์อาจจะต้องคำนวณ Layout, Style และทำการวาดหน้าจอใหม่ (Repaint/Reflow) ทั้งหมดหรือเกือบทั้งหมด ในแอปพลิเคชันสมัยใหม่ที่มีการอัปเดตข้อมูลบ่อยครั้ง (เช่น การแสดงราคาหุ้นแบบ real-time หรือฟีดข่าว) การทำเช่นนี้จะส่งผลให้ประสิทธิภาพลดลงอย่างมากและทำให้ UI กระตุก
เพื่อแก้ปัญหานี้ React ได้นำเสนอแนวคิดที่เรียกว่า Virtual DOM (VDOM) ขึ้นมา Virtual DOM ไม่ใช่เทคโนโลยีของเบราว์เซอร์ แต่เป็นแนวคิดในการเขียนโปรแกรม โดยมันคือ "พิมพ์เขียว" หรือสำเนาของ Real DOM ที่ถูกเก็บไว้ในหน่วยความจำของ JavaScript ในรูปแบบของอ็อบเจกต์ธรรมดาๆ (Plain JavaScript Object) ซึ่งมีน้ำหนักเบาและทำงานได้รวดเร็วกว่าการเข้าถึง Real DOM โดยตรง
ลองนึกภาพว่า Real DOM คืออาคารจริง การจะทุบหรือต่อเติมแต่ละครั้งมีค่าใช้จ่ายสูงและใช้เวลามาก ส่วน Virtual DOM ก็เหมือนกับแบบแปลนของอาคารบนกระดาษ เราสามารถขีดเขียน แก้ไข หรือเปรียบเทียบแบบแปลนหลายๆ ฉบับได้อย่างรวดเร็วโดยไม่มีค่าใช้จ่ายใดๆ
เมื่อมีการเปลี่ยนแปลงเกิดขึ้นในแอปพลิเคชัน React (เช่น ผู้ใช้คลิกปุ่มแล้ว State เปลี่ยน) กระบวนการที่เรียกว่า Reconciliation จะเริ่มต้นขึ้น ซึ่งมีขั้นตอนดังนี้ :
render() (หรือตัวฟังก์ชันคอมโพเนนต์เอง) อีกครั้งเพื่อสร้าง Virtual DOM tree ชุดใหม่ขึ้นมาในหน่วยความจำ// ตัวอย่างการทำงานของ 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 บ่อยครั้งก็ตาม
เมื่อคุณเห็นโค้ด 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!'
);
จุดเด่นที่สุดของ 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 ที่เปลี่ยนแปลงตามข้อมูลได้อย่างง่ายดาย
หากคอมโพเนนต์คือโครงสร้างของแอปพลิเคชัน Props และ State ก็เปรียบเสมือนเลือดที่หล่อเลี้ยงและทำให้โครงสร้างนั้นมีชีวิตและเคลื่อนไหวได้ การทำความเข้าใจความแตกต่างระหว่างสองสิ่งนี้เป็นพื้นฐานที่สำคัญที่สุดในการเขียน React
// 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 ได้
}
useState เพื่อประกาศและจัดการ Stateimport { 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>
);
}
setCount)ในส่วนนี้ เราจะเปลี่ยนจากทฤษฎีมาสู่การปฏิบัติ โดยจะแนะนำคุณทีละขั้นตอนในการติดตั้งเครื่องมือที่จำเป็นทั้งหมดเพื่อสร้างสภาพแวดล้อมการพัฒนา (Development Environment) ที่พร้อมสำหรับเขียน React การมีเครื่องมือที่ดีและทันสมัยจะช่วยให้กระบวนการพัฒนาของคุณรวดเร็วและราบรื่นขึ้นอย่างมาก
Node.js คือ JavaScript runtime environment ที่ทำให้เราสามารถรันโค้ด JavaScript นอกเว็บเบราว์เซอร์ได้ ในบริบทของการพัฒนา React นั้น Node.js ไม่ได้ถูกใช้เพื่อรันโค้ด React ของเราบนเซิร์ฟเวอร์โดยตรง (เว้นแต่จะทำ Server-Side Rendering) แต่มีความสำคัญในฐานะที่เป็น "หัวใจ" ของระบบนิเวศเครื่องมือพัฒนาสมัยใหม่ทั้งหมด ไม่ว่าจะเป็นตัวสร้างโปรเจกต์ (scaffolding tools), ตัวจัดการแพ็กเกจ (package managers), หรือตัวรวมโค้ด (bundlers) ล้วนทำงานบน Node.js ทั้งสิ้น
เมื่อคุณติดตั้ง Node.js คุณจะได้รับเครื่องมือที่ชื่อว่า npm (Node Package Manager) ติดมาด้วยโดยอัตโนมัติ npm คือเครื่องมือจัดการแพ็กเกจที่ใหญ่ที่สุดในโลก ทำหน้าที่ดาวน์โหลดและติดตั้งไลบรารีหรือ "แพ็กเกจ" ต่างๆ ที่โปรเจกต์ของเราต้องใช้ (เรียกว่า dependencies) เช่น ตัว React เอง, React DOM, หรือไลบรารีอื่นๆ
นอกจาก npm แล้ว ยังมีอีกเครื่องมือที่ได้รับความนิยมคือ Yarn ซึ่งพัฒนาโดย Facebook ในอดีต Yarn มีข้อดีเหนือ npm ในเรื่องความเร็วและความเสถียร แต่ในปัจจุบัน npm ได้รับการพัฒนาให้มีประสิทธิภาพทัดเทียมกันมากแล้ว
คำแนะนำสำหรับผู้เริ่มต้น: เพื่อความเรียบง่าย ในบทความนี้เราจะใช้ npm เป็นหลัก เนื่องจากมันถูกติดตั้งมาพร้อมกับ Node.js อยู่แล้ว ไม่ต้องติดตั้งอะไรเพิ่มเติม
# ตรวจสอบเวอร์ชัน Node.js
node -v
# ตรวจสอบเวอร์ชัน npm
npm -v
หากคำสั่งทำงานและแสดงหมายเลขเวอร์ชัน (เช่น v20.11.0 และ 10.2.4) แสดงว่าคุณพร้อมสำหรับขั้นตอนต่อไปแล้ว
สำหรับนักพัฒนาที่ต้องทำงานกับหลายโปรเจกต์ ซึ่งแต่ละโปรเจกต์อาจต้องการ Node.js คนละเวอร์ชัน การติดตั้ง Node.js โดยตรงอาจไม่สะดวกนัก เครื่องมืออย่าง nvm จะเข้ามาช่วยแก้ปัญหานี้ โดยมันอนุญาตให้คุณติดตั้งและสลับเวอร์ชันของ Node.js ไปมาได้อย่างง่ายดายด้วยคำสั่งเพียงไม่กี่บรรทัด อย่างไรก็ตาม สำหรับผู้เริ่มต้น การติดตั้งเวอร์ชัน LTS เพียงเวอร์ชันเดียวก็เพียงพอแล้ว
ในอดีต เครื่องมือมาตรฐานสำหรับสร้างโปรเจกต์ React คือ Create React App (CRA) ซึ่งสร้างโดยทีมงานของ React เอง แต่ในปัจจุบัน (ปี 2026) ชุมชนนักพัฒนาได้หันมานิยมใช้เครื่องมือที่ทันสมัยและรวดเร็วกว่าอย่าง Vite กันอย่างแพร่หลาย
เหตุผลหลักที่ Vite เหนือกว่า CRA อย่างชัดเจนคือเรื่องของ "ความเร็ว" และ "ประสบการณ์การพัฒนา (Developer Experience - DX)" :
vite.config.js โดยไม่ต้อง "eject" เหมือนใน CRAการสร้างโปรเจกต์ React ด้วย Vite นั้นง่ายมาก เพียงแค่ใช้คำสั่งเดียวใน Terminal:
npm create vite@latest my-react-app -- --template react
npm create vite@latest: เป็นคำสั่งในการเรียกใช้เครื่องมือสร้างโปรเจกต์ของ Vite เวอร์ชันล่าสุดmy-react-app: คือชื่อโฟลเดอร์และโปรเจกต์ของคุณ (สามารถเปลี่ยนเป็นชื่ออื่นได้)-- --template react: เป็นการระบุให้ Vite ใช้เทมเพลตสำหรับ React (Vite รองรับเฟรมเวิร์กอื่นๆ ด้วย เช่น Vue, Svelte)หลังจากคำสั่งข้างต้นทำงานเสร็จสิ้น คุณจะได้โฟลเดอร์โปรเจกต์ใหม่ตามชื่อที่ตั้งไว้ ให้ทำตามขั้นตอนต่อไปนี้:
cd my-react-app
package.json และดาวน์โหลดไลบรารีที่จำเป็นทั้งหมดมาเก็บไว้ในโฟลเดอร์ node_modules
npm install
npm run dev
หลังจากรันคำสั่งสุดท้าย Terminal จะแสดงข้อความว่าเซิร์ฟเวอร์พร้อมทำงาน และโดยปกติแล้วเบราว์เซอร์จะเปิดหน้าเว็บขึ้นมาอัตโนมัติที่ URL http://localhost:5173 (หรือพอร์ตอื่นหากพอร์ต 5173 ไม่ว่าง) คุณจะเห็นหน้าต้อนรับของ React + Vite ซึ่งเป็นการยืนยันว่าโปรเจกต์ของคุณพร้อมสำหรับการพัฒนาแล้ว!
เมื่อเปิดโปรเจกต์ที่สร้างโดย 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
src/: นี่คือที่ที่คุณจะใช้เวลาส่วนใหญ่ในการทำงาน โค้ดคอมโพเนนต์, สไตล์, และ logic ต่างๆ จะถูกเก็บไว้ที่นี่src/main.jsx: เป็นไฟล์ JavaScript ไฟล์แรกที่ถูกรัน ทำหน้าที่สำคัญคือการบอก React ว่าจะให้เรนเดอร์คอมโพเนนต์หลัก (<App />) ไปไว้ที่ element ใดใน DOM (ซึ่งก็คือ <div id="root"></div> ใน index.html)src/App.jsx: เป็นคอมโพเนนต์ราก (Root Component) ของแอปพลิเคชัน เปรียบเสมือนคอนเทนเนอร์หลักที่ห่อหุ้มคอมโพเนนต์อื่นๆ ทั้งหมดไว้public/: ใช้สำหรับเก็บไฟล์ที่ไม่ต้องผ่านกระบวนการ build เช่น index.html, ไอคอน, หรือรูปภาพบางประเภทที่ต้องการให้เข้าถึงได้โดยตรงpackage.json: ไฟล์ที่สำคัญมาก ประกอบด้วยชื่อโปรเจกต์, เวอร์ชัน, รายชื่อ dependencies (ไลบรารีที่ใช้), และส่วนของ "scripts" ที่เราใช้รันคำสั่งต่างๆ เช่น npm run dev หรือ npm run buildการมีเครื่องมือที่เหมาะสมจะช่วยให้การเขียนโค้ดของคุณมีประสิทธิภาพและลดข้อผิดพลาดได้มาก
เมื่อเราตั้งค่าโปรเจกต์เรียบร้อยแล้ว ก็ถึงเวลามาทำความรู้จักกับหัวใจของการเขียน React กันจริงๆ ในส่วนนี้ เราจะเริ่มจากพื้นฐานสำหรับผู้ที่คุ้นเคยกับ HTML, CSS และ JavaScript มาแล้ว โดยจะพาทำ Workshop ง่ายๆ เพื่อให้เห็นภาพการทำงานจริง
สิ่งแรกที่ต้องทำคือการ "ล้าง" โปรเจกต์เริ่มต้นให้สะอาด เพื่อให้เราเริ่มต้นจากศูนย์จริงๆ
src/App.jsx แล้วลบโค้ดทั้งหมดออก แล้วแทนที่ด้วยโค้ดนี้:
function App() {
return (
<h1>Hello, React!</h1>
);
}
export default App;
src/App.css และ src/index.css แล้วลบ CSS ทั้งหมดข้างในออกเมื่อคุณบันทึกไฟล์ หน้าเว็บในเบราว์เซอร์ควรจะอัปเดตอัตโนมัติและแสดงข้อความ "Hello, React!" บนพื้นหลังสีขาว นี่คือ Component แรกของคุณ! มันคือฟังก์ชัน JavaScript ที่ return สิ่งที่ดูเหมือน HTML ซึ่งเรียกว่า JSX
ตอนนี้เราจะสร้าง Component ใหม่สำหรับแสดงข้อมูลผู้ใช้ และนำไปใช้ใน App component
src/ สร้างโฟลเดอร์ใหม่ชื่อ componentssrc/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
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 ที่เราสร้างขึ้น
ตอนนี้ UserProfile ของเรามีข้อมูลตายตัวอยู่ข้างใน เราจะทำให้มันยืดหยุ่นขึ้นโดยการรับข้อมูลจากข้างนอกผ่าน Props
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;
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 ที่นำกลับมาใช้ใหม่ได้
Props เหมาะสำหรับข้อมูลที่ส่งจากบนลงล่างและไม่เปลี่ยนแปลง แต่ถ้าเราต้องการให้ Component มี "ความจำ" และเปลี่ยนแปลงได้ตามการกระทำของผู้ใช้ เราต้องใช้ State
เราจะสร้างปุ่มนับเลขง่ายๆ
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;
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 ของคุณโต้ตอบได้
{} เพื่อแทรก JavaScriptตอนนี้เครื่องมือของเราพร้อมแล้ว และได้รู้จักกับพื้นฐานการสร้าง Component, Props, และ State แล้ว ก็ถึงเวลาลงมือสร้างแอปพลิเคชัน Todo List กันจริงๆ ในส่วนนี้ เราจะไม่ได้เริ่มเขียนโค้ดทันที แต่จะประยุกต์ใช้กระบวนการคิดที่เป็นระบบที่เรียกว่า "Thinking in React" ซึ่งเป็นหัวใจสำคัญที่จะช่วยให้คุณสามารถรับมือกับแอปพลิเคชันที่ซับซ้อนขึ้นในอนาคตได้
ออกแบบหน้าตาของแอปพลิเคชัน 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 นี้ เราสามารถแบ่งเป็นคอมโพเนนต์ต่างๆ ได้ดังนี้:
TodoApp (สีเทา): คอมโพเนนต์หลักที่ห่อหุ้มทุกอย่างไว้Header (สีน้ำเงิน): แสดงชื่อของแอปพลิเคชันTodoForm (สีเขียว): ส่วนของ input และปุ่มสำหรับเพิ่ม to-do ใหม่FilterControls (สีส้ม): ปุ่มสำหรับกรองรายการ to-doTodoList (สีม่วง): คอมโพเนนต์ที่แสดงรายการ to-do ทั้งหมดTodoItem (สีเหลือง): แสดง to-do แต่ละรายการ พร้อม checkbox และปุ่มลบ (อาจเป็นส่วนหนึ่งของ TodoList)Footer (สีชมพู): แสดงจำนวนรายการที่เหลืออยู่เมื่อได้รายชื่อคอมโพเนนต์แล้ว เราจะจัดลำดับชั้น (Hierarchy) ของมัน:
TodoApp
├── Header
├── TodoForm
├── FilterControls
├── TodoList
│ ├── TodoItem
│ ├── TodoItem
│ └── ...
└── Footer
การมีแผนผังนี้ในใจจะทำให้ขั้นตอนการเขียนโค้ดชัดเจนและเป็นระบบมากขึ้น
เขียนโค้ดเพื่อแสดงผล UI ตามที่ออกแบบไว้โดยใช้ข้อมูลจำลอง (Mock Data) โดยที่ยังไม่มีการโต้ตอบใดๆ (เช่น คลิกปุ่มแล้วยังไม่มีอะไรเกิดขึ้น) ขั้นตอนนี้เน้นไปที่การส่งข้อมูลผ่าน Props
src/components สร้างไฟล์สำหรับแต่ละคอมโพเนนต์ที่เราออกแบบไว้ เช่น Header.jsx, TodoForm.jsx, TodoList.jsx, TodoItem.jsxsrc/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 },
];
App.jsx แล้วส่งข้อมูล initialTodos ลงไปให้ TodoList ผ่าน props
// src/App.jsx
import TodoList from './components/TodoList';
function App() {
const initialTodos = [ ... ]; // ข้อมูลจากข้อ 2
return (
<div>
{/* ... คอมโพเนนต์อื่นๆ ... */}
<TodoList todos={initialTodos} />
</div>
);
}
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 เราจำเป็นต้องใส่keyprop ที่มีค่าไม่ซ้ำกัน (unique) ให้กับ element หลักของแต่ละ item (ในที่นี้คือ<TodoItem>) React ใช้keyนี้เพื่อติดตามว่า item ไหนมีการเปลี่ยนแปลง, เพิ่ม, หรือลบไป เพื่ออัปเดต DOM ได้อย่างมีประสิทธิภาพ
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';
ระบุว่าข้อมูลส่วนไหนในแอปพลิเคชันที่ต้อง "เปลี่ยนแปลงได้" และ "ถูกจดจำ" เพื่อให้ UI สามารถโต้ตอบกับผู้ใช้ได้
หัวใจสำคัญของการจัดการ State ที่ดีคือการเก็บ State ให้น้อยที่สุดเท่าที่จำเป็น ถามตัวเองด้วยคำถามเหล่านี้สำหรับข้อมูลแต่ละชิ้น:
สิ่งที่เหลืออยู่หลังจากผ่านคำถามเหล่านี้ คือสิ่งที่ควรจะเป็น State
สรุปแล้ว State ที่จำเป็นสำหรับแอปของเราคือ: 1) รายการ to-do ทั้งหมด, 2) ข้อความใน input, และ 3) ค่า filter ปัจจุบัน
ตัดสินใจว่าคอมโพเนนต์ใดควรเป็น "เจ้าของ" (Owner) ของ State แต่ละตัวที่เราหามาได้ในขั้นตอนที่แล้ว
React ใช้การไหลของข้อมูลทิศทางเดียว (one-way data flow) ดังนั้นการหาบ้านที่เหมาะสมให้ State จึงสำคัญมาก เราใช้เทคนิคที่เรียกว่า "Lifting State Up" ซึ่งมีขั้นตอนดังนี้:
TodoForm (เพื่อแสดงค่าใน input)TodoForm เองTodoFormTodoList (ต้องใช้รายการ to-do เพื่อแสดงผล), FilterControls (ต้องใช้ค่า filter ปัจจุบันเพื่อไฮไลท์ปุ่ม), Footer (ต้องใช้รายการ to-do เพื่อนับจำนวน)TodoAppTodoAppตอนนี้เราจะแก้ไข 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>
);
}
ทำให้คอมโพเนนต์ลูกสามารถอัปเดต State ที่อยู่ในคอมโพเนนต์แม่ได้ เพื่อให้แอปพลิเคชันโต้ตอบกับผู้ใช้ได้อย่างสมบูรณ์
เนื่องจากคอมโพเนนต์ลูกไม่สามารถแก้ไข State ของแม่ได้โดยตรง วิธีการคือ แม่จะต้องส่ง "ฟังก์ชันสำหรับอัปเดต State" ลงไปให้ลูกผ่านทาง Props
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
};
// ...
}
handleAddTodo และ State ที่เกี่ยวข้องลงไปให้ TodoForm
// ใน return ของ App.jsx
<TodoForm
newTodoText={newTodoText}
onNewTodoTextChange={setNewTodoText}
onAddTodo={handleAddTodo}
/>
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>
การเขียนโค้ดที่ทำงานได้เป็นเพียงครึ่งหนึ่งของงานพัฒนามืออาชีพ อีกครึ่งที่สำคัญไม่แพ้กันคือการเขียนเทสต์ (Test) เพื่อให้มั่นใจว่าโค้ดของเราทำงานได้ถูกต้องตามที่คาดหวัง, ไม่พังเมื่อมีการแก้ไขในอนาคต (Regression), และทำให้เรากล้าที่จะ Refactor โค้ดให้ดีขึ้น ในส่วนนี้ เราจะมาเรียนรู้พื้นฐานการทดสอบคอมโพเนนต์ React กัน
เป็นแนวคิดที่ช่วยจัดลำดับความสำคัญของการเขียนเทสต์ แบ่งออกเป็น 3 ระดับหลักๆ :
ใน Workshop นี้ เราจะเน้นไปที่ Unit Tests และ Integration Tests สำหรับคอมโพเนนต์ ซึ่งเป็นจุดที่ให้ผลตอบแทนคุ้มค่าที่สุดสำหรับนักพัฒนา React
ในระบบนิเวศของ React เครื่องมือที่เราจะใช้เป็นหลักคือ:
expect() และความสามารถในการจำลอง (Mocking)fireEvent แบบดั้งเดิม เพราะมันจะจำลอง event ต่างๆ ที่เกิดขึ้นตามลำดับเหมือนที่เบราว์เซอร์ทำจริงๆข่าวดีคือ โปรเจกต์ที่สร้างด้วย Vite จะมีการตั้งค่า Jest (หรือ Vitest ซึ่งเป็นทางเลือกที่เข้ากันได้) และ React Testing Library มาให้เบื้องต้นแล้ว
ทดสอบคอมโพเนนต์ง่ายๆ ที่ไม่มี State และไม่มีการโต้ตอบ เช่น Header เพื่อให้แน่ใจว่ามันแสดงผลข้อความที่ถูกต้อง
เป็นโครงสร้างการเขียนเทสต์ที่ดีและอ่านง่าย :
สร้างไฟล์เทสต์สำหรับคอมโพเนนต์ 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 ของเราทำงานได้ตามที่คาดหวัง
ทดสอบคอมโพเนนต์ที่มีการโต้ตอบกับผู้ใช้และมีการเปลี่ยนแปลง State เช่น คอมโพเนนต์ Counter ที่เราสร้างไว้ก่อนหน้านี้
เราจะใช้ user-event เพื่อจำลองการคลิกของผู้ใช้ และตรวจสอบว่า UI อัปเดตตาม State ที่เปลี่ยนไปอย่างถูกต้อง
// 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 บนหน้าจอมากที่สุด
ในแอปพลิเคชันจริง คอมโพเนนต์ของเรามักจะต้องพึ่งพาสิ่งภายนอก เช่น การเรียก API จาก Backend, การใช้ฟังก์ชันจากไลบรารีอื่น, หรือการรับฟังก์ชันผ่าน Props ในการทำ Unit/Integration Test เราต้องการทดสอบ "ตรรกะของคอมโพเนนต์เราเอง" โดยแยกตัวออกจากความไม่แน่นอนของระบบภายนอก (เช่น Backend อาจจะล่ม) การ Mocking คือเทคนิคในการสร้าง "ตัวปลอม" ของ dependencies เหล่านั้นขึ้นมา เพื่อให้เราควบคุมผลลัพธ์ของมันได้ในการทดสอบ
สมมติเรามีคอมโพเนนต์ 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 ผ่านกระบวนการคิดที่เป็นระบบ, และปิดท้ายด้วยการเขียนเทสต์เพื่อสร้างความมั่นใจในคุณภาพของโค้ด
เส้นทางของ React ยังมีอะไรให้เรียนรู้อีกมากมาย นี่คือหัวข้อและเครื่องมือที่คุณควรศึกษาต่อเพื่อต่อยอดความรู้ของคุณ:
useState เหมาะสำหรับ State ภายในคอมโพเนนต์หรือแอปขนาดเล็ก แต่เมื่อแอปพลิเคชันซับซ้อนขึ้น การจัดการ State ที่ต้องใช้ร่วมกันข้ามหลายๆ คอมโพเนนต์ (Global State) จะกลายเป็นเรื่องท้าทาย ไลบรารีอย่าง Zustand (เรียบง่ายและทันสมัย) หรือ Redux Toolkit (มาตรฐานอุตสาหกรรมสำหรับแอปขนาดใหญ่) จะเข้ามาช่วยแก้ปัญหานี้ React.memo, useCallback, และ useMemo เพื่อป้องกันการ re-render ที่ไม่จำเป็นReact.lazy และ Suspense เพื่อแบ่งโค้ดออกเป็นส่วนๆ และโหลดเฉพาะเมื่อจำเป็น ช่วยลดขนาด bundle เริ่มต้น ขอให้สนุกกับการเดินทางในโลกของ React! การเรียนรู้และฝึกฝนอย่างสม่ำเสมอคือหนทางสู่การเป็นนักพัฒนาที่ยอดเยี่ยม
ส่วนนี้คือสรุปขั้นตอนการปฏิบัติทั้งหมดในรูปแบบตารางและรายการตรวจสอบ เพื่อให้คุณสามารถกลับมาทบทวนและใช้เป็นแนวทางในการสร้างโปรเจกต์ของคุณเองได้อย่างรวดเร็ว
| หมวดหมู่ | รายการ | เวอร์ชันที่แนะนำ | วิธีตรวจสอบ |
|---|---|---|---|
| ฮาร์ดแวร์ | ระบบปฏิบัติการ | Windows, macOS, หรือ Linux | - |
| ซอฟต์แวร์ | Node.js |
v18.x.x หรือสูงกว่า (LTS) |
node -v |
npm |
v9.x.x หรือสูงกว่า |
npm -v |
|
| ความรู้ | JavaScript (ES6+) |
เข้าใจพื้นฐาน (ตัวแปร, ฟังก์ชัน, array, object) | - |
HTML/CSS |
เข้าใจการสร้างโครงสร้างและตกแต่งเว็บเบื้องต้น | - |
อ่านและทำความเข้าใจแนวคิด: Component, Props, State, และ Virtual DOM จากส่วนต้นของบทความ
ดาวน์โหลดและติดตั้งเวอร์ชัน LTS จาก nodejs.org
ตรวจสอบการติดตั้งด้วยคำสั่ง:
node -v
npm -v
เปิด 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
ภารกิจ: สร้าง Header component เพื่อแสดงชื่อแอปพลิเคชัน
การปฏิบัติ:
src/ สร้างโฟลเดอร์ components และสร้างไฟล์ Header.jsxfunction Header() {
return <h1>My Todo List</h1>;
}
export default Header;
src/App.jsx ลบโค้ดเริ่มต้นออก และนำเข้า Header มาใช้งาน:
import Header from './components/Header';
function App() {
return (
<div>
<Header />
</div>
);
}
export default App;
การตรวจสอบ: รีเฟรชเบราว์เซอร์ ควรเห็นข้อความ "My Todo List"
ภารกิจ: สร้างปุ่มนับเลขที่เมื่อคลิกแล้วตัวเลขจะเพิ่มขึ้น
การปฏิบัติ:
src/components/Counter.jsxuseState Hook เพื่อสร้าง state counthandleClick เพื่อเพิ่มค่า countcount และปุ่มสำหรับคลิก:
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;
Counter ไปใช้ใน App.jsxการตรวจสอบ: คลิกที่ปุ่ม แล้วตัวเลขบนหน้าจอควรจะเพิ่มขึ้น
ภารกิจ: เขียนเทสต์เพื่อยืนยันว่า Header แสดงผลข้อความถูกต้อง
การปฏิบัติ:
src/components/Header.test.jsximport { 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();
});
ใน Terminal รันคำสั่ง:
npm test
การตรวจสอบ: Terminal ควรแสดงผลว่าเทสต์ทั้งหมดผ่าน
รันคำสั่ง Build:
npm run build
การตรวจสอบ: จะมีโฟลเดอร์ dist ถูกสร้างขึ้นในโปรเจกต์
npm run dev ได้Header component และแสดงผลบนหน้าจอได้อย่างถูกต้องCounter component และทำงานได้ (คลิกแล้วเลขเพิ่ม)Header และรันเทสต์ผ่านdist| ปัญหา | สาเหตุที่เป็นไปได้ | แนวทางแก้ไข |
|---|---|---|
คำสั่ง 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 อีกครั้ง |