หัวข้อ 15 · 22 นาที · เสริม
React พื้นฐาน
นึกภาพก่อน
ในบท DOM เราสั่งทีละขั้น: "สร้าง li ใส่ข้อความ ต่อเข้า ul แล้วแก้หัวข้อ" ถ้าหน้าใหญ่ขึ้น โค้ดแบบนี้จะยุ่งและพลาดง่าย
React กลับวิธีคิด: เรา บอกว่าหน้าจอควรเป็นอย่างไรเมื่อข้อมูลเป็นแบบนี้ แล้ว React จัดการแก้ DOM ให้เอง เหมือนเปลี่ยนจากการสั่งคนขับ "เลี้ยวซ้าย ตรงไป เลี้ยวขวา" เป็นการบอกแค่ ปลายทาง
UI = f(state) — หน้าจอเป็นผลลัพธ์ของข้อมูล เมื่อข้อมูลเปลี่ยน หน้าจอเปลี่ยนตาม
Component
component คือ function ที่คืน UI เป็นชิ้นส่วนที่นำกลับมาใช้ซ้ำได้ และประกอบกันเป็นหน้า
function Welcome() {
return <h1>Hello, React</h1>;
}
function App() {
return (
<div>
<Welcome />
<Welcome />
</div>
);
}
- ชื่อ component ต้องขึ้นต้นด้วยตัวพิมพ์ใหญ่ (PascalCase) —
<welcome />จะถูกมองเป็น tag ของ HTML - ใช้ component เหมือน tag:
<Welcome />
JSX
syntax ที่หน้าตาเหมือน HTML แต่เขียนอยู่ใน JavaScript ถูกแปลงเป็นการเรียก function ก่อนรัน
| กฎ | ตัวอย่าง |
|---|---|
| คืนได้ root element เดียว | ครอบด้วย <div> หรือ Fragment <>…</> |
แทรก JavaScript ด้วย { } | <p>{user.name}</p>, <p>{1 + 2}</p> |
class เขียนเป็น className | <div className="card"> |
| attribute เป็น camelCase | onClick, htmlFor |
| ทุก tag ต้องปิด | <img />, <br /> |
style รับ object | style={{ color: 'red' }} |
ใน { } ใส่ได้เฉพาะ expression (สิ่งที่ให้ค่า) ใส่ if หรือ for ไม่ได้ จึงใช้ ternary, && และ map แทน
Props
props คือข้อมูลที่ parent ส่งให้ child component เหมือน argument ของ function
function Card({ title, price }) { // destructuring ที่ parameter
return (
<div className="card">
<h2>{title}</h2>
<p>{price.toLocaleString('en-US')} บาท</p>
</div>
);
}
function App() {
return (
<>
<Card title="Keyboard" price={1200} />
<Card title="Mouse" price={450} />
</>
);
}
- ส่ง string ด้วย
"…"ส่งค่าอื่น (number, boolean, object, function) ด้วย{ } - props อ่านได้อย่างเดียว child แก้ props ไม่ได้
- ข้อมูลไหล ทางเดียว: จาก parent ลงไป child
childrenคือ props พิเศษ: สิ่งที่อยู่ระหว่าง tag เปิดกับปิดของ component
State และ useState
state คือข้อมูลที่ component จำไว้เองและเปลี่ยนได้ เมื่อ state เปลี่ยน React render component ใหม่
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // array destructuring
return (
<button onClick={() => setCount(count + 1)}>
กดแล้ว {count} ครั้ง
</button>
);
}
useState(ค่าเริ่มต้น) คืน array สองช่อง: ค่าปัจจุบัน และ function สำหรับเปลี่ยนค่า
| กฎ | เหตุผล |
|---|---|
| เปลี่ยน state ด้วย setter เท่านั้น | count = count + 1 ไม่ทำให้ React render ใหม่ |
| ห้ามแก้ของเดิม (immutability) | setItems([...items, x]) ไม่ใช่ items.push(x) |
| state ใหม่มีผลใน render ครั้งถัดไป | หลัง setCount ทันที count ยังเป็นค่าเดิม |
| อัปเดตจากค่าเดิมใช้รูป function | setCount(c => c + 1) |
Props กับ State
| Props | State | |
|---|---|---|
| มาจาก | parent ส่งมา | component สร้างเอง |
| แก้ได้ไหม | ไม่ได้ | ได้ ผ่าน setter |
| เปลี่ยนแล้ว | render ใหม่ | render ใหม่ |
แสดงรายการด้วย map
function TodoList() {
const todos = [
{ id: 1, text: 'อ่าน HTTP' },
{ id: 2, text: 'ทำ lab React' },
];
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
mapแปลง array ของข้อมูลเป็น array ของ element (ตรงกับ challenge<li>ในบท Array)- ทุก element ในรายการต้องมี
keyที่ ไม่ซ้ำและคงที่ เพื่อให้ React รู้ว่าตัวใดเป็นตัวใดเมื่อรายการเปลี่ยน - ใช้ id ของข้อมูลเป็น key เลี่ยงการใช้ index เมื่อรายการถูกเพิ่ม ลบ หรือเรียงใหม่ได้
แสดงแบบมีเงื่อนไข
{isLoggedIn ? <p>ยินดีต้อนรับ</p> : <p>กรุณา login</p>}
{items.length === 0 && <p>ไม่มีรายการ</p>}
Event
function Form() {
const [name, setName] = useState('');
const handleSubmit = (event) => {
event.preventDefault(); // ไม่ให้ form reload หน้า
alert(`Hello ${name}`);
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">ส่ง</button>
</form>
);
}
- ส่ง ตัว function:
onClick={handleClick}ไม่ใช่onClick={handleClick()} - input ที่
valueผูกกับ state และแก้ผ่านonChangeเรียกว่า controlled component
useEffect
ใช้ทำงานที่ อยู่นอกการคำนวณ UI (side effect) เช่น ดึงข้อมูลจาก API, ตั้ง timer
import { useState, useEffect } from 'react';
function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []); // dependency array
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
| dependency array | effect ทำงานเมื่อ |
|---|---|
| ไม่ใส่ | หลัง ทุก render |
[] | ครั้งเดียว หลัง render แรก |
[a, b] | render แรก และเมื่อ a หรือ b เปลี่ยน |
function ที่ effect return คือ cleanup ทำงานก่อน effect รอบถัดไปและตอน component ถูกถอดออก
กฎของ Hooks
- เรียก Hook ที่ ระดับบนสุด ของ component เท่านั้น — ไม่เรียกใน
if, loop หรือ function ซ้อน - เรียกได้ใน component หรือ custom Hook (function ที่ชื่อขึ้นต้นด้วย
use)
ตัวอย่างไล่ทีละขั้น
โจทย์: กดปุ่มหนึ่งครั้ง หน้าจอแสดงอะไร และ console พิมพ์อะไร
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
console.log(count);
};
return <button onClick={handleClick}>{count}</button>;
}
- render แรก:
count = 0ปุ่มแสดง 0 - กดปุ่ม →
handleClickทำงาน โดยcountใน function นี้คือ 0 (ค่าของ render นี้) setCount(0 + 1)→ ขอให้ render ครั้งถัดไปใช้ 1setCount(0 + 1)→ ขอ 1 อีกครั้ง (ไม่ใช่ 2 เพราะcountยังเป็น 0)console.log(count)→ พิมพ์ 0 เพราะ state ยังไม่เปลี่ยนใน render นี้- React render ใหม่ด้วย
count = 1→ ปุ่มแสดง 1
ถ้าต้องการเพิ่มสองจริง ๆ: setCount(c => c + 1) สองครั้ง → ได้ 2
จุดที่มักพลาด
1. ตั้งชื่อ component ด้วยตัวพิมพ์เล็ก
React มองเป็น tag ของ HTML
2. แก้ state ตรง ๆ
count++ หรือ items.push(x) ไม่ทำให้ render ใหม่
3. ลืม key ในรายการ
React เตือน และอาจ render ผิดเมื่อรายการเปลี่ยน
4. เรียก function แทนการส่ง function
onClick={handle()} ทำงานตอน render ไม่ใช่ตอนคลิก
5. เขียน class แทน className
ใน JSX ใช้ className
6. ลืม dependency array ของ useEffect
effect ที่ setState จะวน render ไม่รู้จบ
7. คิดว่า child แก้ props ได้
props อ่านได้อย่างเดียว ถ้าต้องการให้ child เปลี่ยนข้อมูลของ parent ให้ parent ส่ง function ลงไปเป็น props
8. คืนหลาย element โดยไม่มีตัวครอบ
ต้องมี root เดียว ใช้ <>…</>
ที่มา: เนื้อหาเสริม ไม่มีเอกสารใน docs/WEB (ต่อยอดจาก JavaScript - for React.pdf)