React Fundamentals
JSX — Syntax Sugar ที่ไม่ใช่ HTML
JSX ดูเหมือน HTML แต่จริงๆ คือ syntax sugar ของ React.createElement() — compiler (Babel/
SWC) แปลง JSX เป็น JavaScript function call ธรรมดาก่อนรันจริง
// สิ่งที่เขียน
const el = <h1 className="title">Hello</h1>;
// สิ่งที่ compiler แปลงให้จริงๆ (classic runtime)
const el = React.createElement('h1', { className: 'title' }, 'Hello');
"JSX คือใบสั่งทำ ไม่ใช่ของจริง" — เวลาเขียน <div>Hello</div> ไม่ได้สร้าง DOM element ทันที
มันแค่สร้าง "ใบสั่ง" (object ธรรมดา) บอกว่า "อยากได้ div ที่มีข้อความ Hello" แล้วให้ React เป็น
คนไปสร้าง DOM จริงให้ทีหลัง
ตั้งแต่ React 17+ มี "new JSX transform" ที่เปลี่ยนวิธี compile JSX ไปอีกแบบ — ไม่ต้อง
import React from 'react' ในทุกไฟล์ที่ใช้ JSX อีกต่อไป (เพราะ compiler แทรก import พิเศษจาก
react/jsx-runtime ให้เองอัตโนมัติ ไม่ใช่เรียก React.createElement ตรงๆ) หลายคนยังเข้าใจผิด
ว่า "ต้อง import React เสมอเพราะ JSX ใช้ React" ทั้งที่จริงๆ เป็นแค่ requirement ของ transform
รุ่นเก่า (classic runtime) เท่านั้น — เช็คได้จาก tsconfig.json/babel.config.js ว่าตั้ง
"jsx": "react-jsx" (new) หรือ "jsx": "react" (classic) อยู่
Component, Props, State
Component คือฟังก์ชัน JavaScript ธรรมดาที่ return JSX — Props คือข้อมูลที่ "รับจากข้างนอก" (read-only, คล้าย parameter ของฟังก์ชัน) ส่วน State คือข้อมูลที่ component "จำเอง" และ เปลี่ยนแปลงได้ (เปลี่ยนแล้ว re-render)
function Greeting({ name }) { // name คือ prop
const [count, setCount] = useState(0); // count คือ state
return (
<div>
<p>Hello, {name}!</p>
<button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
</div>
);
}
"Props = จดหมายที่ส่งเข้ามา (เปิดอ่านได้ ฉีกแก้ไม่ได้), State = สมุดบันทึกส่วนตัว (เขียนทับได้ เอง)" — component แก้ไข props ของตัวเองไม่ได้เด็ดขาด (one-way data flow) แต่แก้ state ของ ตัวเองได้เต็มที่ผ่าน setter function
setCount(count + 1) ที่เรียกติดกันหลายครั้งใน event handler เดียวกัน ไม่ได้บวกสะสมทีละ
ครั้งเสมอไป เพราะ count ใน closure นั้นเป็นค่า "ตอน render ครั้งนั้น" (snapshot) ไม่ใช่ค่า
ล่าสุดแบบ real-time:
function handleClick() {
setCount(count + 1); // count เป็น 0 → ตั้งใจเป็น 1
setCount(count + 1); // count ยังเป็น 0 (closure เดิม) → ตั้งใจเป็น 1 อีกรอบ
setCount(count + 1); // ผลลัพธ์สุดท้าย = 1 ไม่ใช่ 3!
}
ต้องใช้ functional update (setCount(c => c + 1)) ถึงจะได้ค่าล่าสุดจริงทุกครั้ง เพราะ
React รับประกันว่า c ใน callback นี้คือ state ล่าสุด ไม่ใช่ snapshot — เป็นกับดักที่ React
developer จำนวนมากเจอครั้งแรกตอนเขียน debounce/batch update แล้วงงว่าทำไมค่าไม่ตรงที่คิดไว้
Conditional Rendering
function Status({ isOnline }) {
return (
<div>
{isOnline ? <span>🟢 Online</span> : <span>⚫ Offline</span>}
{isOnline && <span>Active now</span>}
</div>
);
}
"&& สำหรับ 'มีก็โชว์ ไม่มีก็เงียบ', ternary สำหรับ 'เลือกข้างใดข้างหนึ่งเสมอ'" — ใช้ &&
เมื่อมีแค่ทางเลือกเดียวที่อยากโชว์ ใช้ ternary (? :) เมื่อต้องเลือกระหว่าง 2 ทางเสมอ
{count && <span>...</span>} มีกับดักที่เจอบ่อยมาก: ถ้า count เป็น 0 (ไม่ใช่
false) React จะ render เลข 0 ออกมาบนหน้าจอจริงๆ (ไม่ใช่ "เงียบ" ตามที่ตั้งใจ) เพราะ
0 && <span> evaluate เป็น 0 ซึ่งเป็นค่าที่ render ได้ใน JSX (ต่างจาก false/null/
undefined ที่ React จงใจไม่ render) วิธีแก้คือบังคับให้เป็น boolean จริงๆ ก่อนเสมอ:
{count > 0 && <span>...</span>} หรือ {!!count && <span>...</span>} — เป็นบั๊กที่ขึ้น
เลข "0" แปลกๆ กลางหน้าเว็บที่คนเจอกันบ่อยมากจนเป็นมีมในวงการ React
List & Key
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
"key คือเลขบัตรประชาชนของแต่ละ item ไม่ใช่เลขคิว" — React ใช้ key เพื่อ "จำ" ว่า item ไหนคือ item เดิมข้าม render แต่ละครั้ง (เหมือนบัตรประชาชนที่ติดตัวไปตลอด) ไม่ใช่แค่เลขลำดับที่เปลี่ยน ไปเรื่อยๆ ตามตำแหน่ง (เลขคิว)
การใช้ index เป็น key (key={index}) ใช้ได้ปลอดภัยก็ต่อเมื่อ list นั้นไม่เรียงลำดับใหม่,
ไม่กรอง, ไม่เพิ่ม/ลบ item กลาง list เท่านั้น — ถ้า list เปลี่ยนลำดับได้ (เช่น sort, filter,
insert กลาง list) การใช้ index เป็น key จะทำให้ React จับคู่ DOM element ผิดตัวกับ data
ผิดตัว เพราะ index ของ item เดิมเปลี่ยนไปตามตำแหน่งใหม่ ผลที่ตามมาคือ component state
ภายใน (เช่น ค่าใน <input> ที่ user พิมพ์ไว้) จะ "เดิน" ไปติดกับ item ผิดตัว ทั้งที่ data
ที่แสดงดูเหมือนถูกต้อง — เป็นบั๊กที่ซ่อนลึกมากเพราะ UI ดูปกติแต่ state ข้างในสลับกันแล้ว ต้องใช้
unique ID ที่ผูกกับตัวข้อมูลจริง (เช่น todo.id จาก database) ไม่ใช่ตำแหน่งใน array เสมอ
JSX compiles to React.createElement() calls (or the newer jsx-runtime import, which is why
modern React no longer requires importing React in every JSX file). Props are read-only input;
state is component-owned and mutable via its setter, but state setters use a closure snapshot —
calling setCount(count + 1) multiple times in one handler won't stack as expected; use the
functional updater setCount(c => c + 1) instead. Conditional rendering with && has a classic
footgun: count && <Component /> renders a literal 0 on screen when count is falsy-but-
numeric zero, since React renders numbers but not false/null/undefined. List keys must be
stable, data-bound identifiers (not array index) whenever a list can reorder, filter, or splice —
otherwise React misattributes internal state (like input field values) to the wrong item even
though the displayed data looks correct.