Component Patterns
Composition over Inheritance
React ไม่สนับสนุนการสืบทอด component ด้วย extends (ต่าง class ทั่วไป) — ใช้
composition (ประกอบ component เข้าด้วยกันผ่าน children/props) แทน
function Card({ children }) {
return <div className="card">{children}</div>;
}
function ProfileCard() {
return (
<Card>
<Avatar />
<UserName />
</Card>
);
}
"React ประกอบ Lego ไม่ต่อสายพันธุ์" — แทนที่จะสร้าง SpecialCard extends Card (สืบทอด
สายพันธุ์) React ใช้วิธี "เอาชิ้นส่วนมาประกอบกัน" (<Card><Avatar /></Card>) ยืดหยุ่นกว่า
เพราะสลับชิ้นส่วนข้างในได้อิสระโดยไม่ต้องแก้ class แม่
เหตุผลที่ React team เลือก composition over inheritance ไม่ใช่แค่เรื่องสไตล์ — มันมาจาก
ข้อจำกัดจริงของ inheritance ใน UI framework: ถ้า SpecialButton extends Button แล้ว React
team แก้ internal implementation ของ Button ภายหลัง (เช่น เปลี่ยนจาก class component เป็น
function component) โค้ดที่สืบทอดจาก Button ทั้งหมดจะพังทันที เพราะผูกติดกับ
implementation detail ภายใน ส่วน composition ผูกแค่กับ "public interface" (props ที่รับ)
ซึ่งเสถียรกว่ามาก — React core team ยืนยันในเอกสารทางการว่าหาสถานการณ์ที่ inheritance
ให้ประโยชน์เหนือ composition ใน React ไม่เจอเลยตลอดหลายปีที่ดูแล framework นี้
Lifting State Up
เมื่อ component สองตัวต้อง share state เดียวกัน ให้ย้าย state ขึ้นไปอยู่ที่ parent ร่วม (ไม่ใช่ copy state ไว้ 2 ที่แล้วพยายาม sync กัน)
function Parent() {
const [value, setValue] = useState('');
return (
<>
<Input value={value} onChange={setValue} />
<Preview text={value} />
</>
);
}
"State ที่ใช้ร่วมกัน ต้องมี 'เจ้าของบ้าน' คนเดียว" — ถ้าลูกสองคน (component) ต้องใช้ของชิ้น เดียวกัน ให้ของนั้นอยู่กับพ่อแม่ (parent) แล้วแจกให้ลูกยืมใช้ (props) ไม่ใช่ให้ลูกคนละคนถือของ คนละชิ้นแล้วพยายามทำให้เหมือนกันอยู่ตลอด (sync สอง state)
Lifting state up ที่ทำมากเกินไปในแอปใหญ่สร้างปัญหาที่เรียกว่า "prop drilling" — state
ต้องถูกส่งผ่าน props ลงไปหลายชั้น component ที่ "ไม่ได้ใช้" state นั้นเลย แค่ทำหน้าที่ส่งต่อ
(pass-through) ทำให้ component กลางทางผูกติดกับ shape ของ data ที่ตัวเองไม่เกี่ยวข้องโดยตรง
วิธีแก้มาตรฐานคือ Context API (ย้าย state ไปอยู่ "กลางอากาศ" ที่ลูกหลานทุกชั้นเข้าถึงได้
ตรงๆ โดยไม่ต้องส่งผ่านมือทีละชั้น) แต่ Context เองก็มีข้อเสีย: component ที่ useContext
จะ re-render ทุกครั้งที่ context value เปลี่ยน แม้จะใช้แค่บาง field ของ value นั้น —
ทีมใหญ่จึงมักผสม local state (สำหรับ UI state เฉพาะจุด) + Context (สำหรับ state ที่ share
กว้างจริงๆ) + state management library ภายนอก (Zustand, Redux ที่ optimize การ re-render
ได้ดีกว่า Context ดิบๆ) ตามความเหมาะสมของแต่ละชั้นข้อมูล
Children, Render Props, Compound Components
// Render props — ส่ง "วิธีเรนเดอร์" เป็น prop
<DataFetcher url="/api/user" render={(data) => <UserCard user={data} />} />
// Compound components — หลาย component ทำงานร่วมกันผ่าน implicit context
<Tabs>
<Tabs.List>
<Tabs.Tab>Profile</Tabs.Tab>
<Tabs.Tab>Settings</Tabs.Tab>
</Tabs.List>
<Tabs.Panels>
<Tabs.Panel>...</Tabs.Panel>
<Tabs.Panel>...</Tabs.Panel>
</Tabs.Panels>
</Tabs>
"Compound component คือรีโมททีวีกับทีวี — แยกกันแต่คุยกันผ่านสัญญาณที่มองไม่เห็น" —
<Tabs.Tab> ไม่ได้รับ "ว่าตัวเองคือ tab ไหน" ผ่าน prop ตรงๆ แต่คุยกับ <Tabs> แม่ผ่าน
Context ที่ซ่อนอยู่ข้างใน (มองจากข้างนอกดู "เป็นธรรมชาติ" เหมือน HTML <select><option>)
Render props pattern เคยเป็นวิธีมาตรฐานในการ "share stateful logic" ข้าม component ก่อนที่
hooks จะเกิดขึ้น (React 16.8) — ปัญหาของมันคือสร้าง "wrapper hell" เวลาต้อง share logic
หลายตัวพร้อมกัน (<DataFetcher><Authenticated><ThemeProvider>... ซ้อนกันเป็นชั้นๆ) Custom
hooks คือสิ่งที่เข้ามาแทนที่ render props สำหรับ use case นี้โดยเฉพาะ เพราะ share logic
ได้โดยไม่ต้องเพิ่ม component ซ้อนใน tree เลย (const data = useDataFetcher(url) เรียกตรงๆ
ไม่มี wrapper) — แต่ compound component pattern ยังไม่มีอะไรมาแทนที่ เพราะมันแก้ปัญหา
คนละแบบ (ออกแบบ public API ที่อ่านง่ายเป็นธรรมชาติ ไม่ใช่แค่ share logic) ทีม React สมัยใหม่
ส่วนใหญ่เลยใช้ "custom hooks สำหรับ logic sharing" คู่กับ "compound components สำหรับ UI
component library" ตามจุดประสงค์ที่ต่างกัน ไม่ใช่เลือกแบบใดแบบหนึ่งแทนกันทั้งหมด
React favors composition over inheritance — component APIs stay stable because they depend only
on props, not internal implementation details that could change. Lifting state up centralizes
shared state in a common parent rather than syncing duplicated copies, but overdoing it causes
prop drilling; Context solves that but re-renders every consumer on any value change, so large
apps typically mix local state, Context, and external state libraries by scope. Render props
were the pre-hooks way to share stateful logic but caused "wrapper hell" when composed — custom
hooks replaced that use case cleanly, while compound components (implicit Context between
siblings, e.g. <Tabs><Tabs.Tab>) remain the standard for building natural-feeling component
library APIs, since they solve a different problem (API ergonomics, not logic sharing).