Rendering & Reconciliation
render ไม่ใช่ “update DOM”
หัวข้อที่มีชื่อว่า “render ไม่ใช่ “update DOM””ความเข้าใจผิดที่พบบ่อย: “re-render” แปลว่า React แตะ DOM ไม่ใช่ React แบ่งงานเป็นสอง phase ที่ต่างกันชัดเจน:
flowchart LR trigger["state change / parent re-render"] --> render["Render phase (call components, build new tree) — pure, no DOM"] render --> recon["Reconciliation (diff new tree vs old)"] recon --> commit["Commit phase (apply minimal DOM changes) — side effects here"]
- Render phase — React เรียก component ของคุณเพื่อสร้าง tree ของ element ใหม่ นี่คือการคำนวณล้วน ๆ: ไม่แตะ DOM และ React หยุดหรือทิ้งงานนี้ได้
- Commit phase — React เทียบ tree ใหม่กับอันก่อนหน้า คำนวณชุดการแก้ DOM ที่ น้อยที่สุด แล้ว apply
ดังนั้น re-render ที่ให้ output เหมือนเดิมจะทำให้ DOM เปลี่ยน ศูนย์ จุด การ render ค่อนข้างถูก ส่วนที่แพง (แตะ DOM) เกิดเฉพาะกับสิ่งที่เปลี่ยนจริงเท่านั้น
Reconciliation: diff สอง tree
หัวข้อที่มีชื่อว่า “Reconciliation: diff สอง tree”การเทียบ tree ใด ๆ สองอันขึ้นชื่อว่าแพง (O(n³)) React ทำให้เร็ว (O(n)) ด้วย heuristic สองข้อ:
- type ต่างกัน → ทิ้ง ถ้า
typeของ element เปลี่ยน (<div>กลายเป็น<span>หรือComponentAกลายเป็นComponentB) React จะ unmount subtree เก่าทั้งอันแล้วสร้างใหม่ทั้งหมด — รวมถึงทำลาย state ของตัวเองด้วย - type เดียวกัน → เก็บ node ไว้ update props ถ้า type เหมือนเดิม React เก็บ DOM node เดิมและ state ของตัวเองไว้ แล้ว update แค่ props ที่เปลี่ยน
// Same type across renders → the <input> keeps its DOM node and its focus/value.{isEditing ? <input value={text} /> : <input value={text} disabled />}
// Different type → React destroys the first and mounts the second (state lost).{isEditing ? <input value={text} /> : <p>{text}</p>}key: identity สำหรับ list
หัวข้อที่มีชื่อว่า “key: identity สำหรับ list”สำหรับ list ของ sibling ชนิดเดียวกัน React ต้องรู้ว่าไอเทมไหนเป็นไอเทมไหนข้าม render โดย default React จับคู่ด้วย ตำแหน่ง (index) — ซึ่งพังทันทีที่ไอเทม reorder แทรก หรือลบ key ให้ identity ที่คงที่กับแต่ละไอเทม เพื่อให้ React ติดตามแต่ละไอเทมได้
// ✅ Stable identity — React follows each todo even when the list reorders.{todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)}
// ⚠️ Index as key — reorder/insert/delete and React mismatches items,// corrupting per-item state (a checked box jumps to the wrong row).{todos.map((todo, i) => <TodoItem key={i} todo={todo} />)}flowchart TB before["Before: [A, B, C] keys a,b,c"] -->|prepend Z| after["After: [Z, A, B, C] keys z,a,b,c"] after --> good["With stable keys: A,B,C reused, Z mounted"] before2["Before: [A, B, C] key = index 0,1,2"] -->|prepend Z| after2["After: index 0,1,2,3"] after2 --> bad["With index keys: every item mismatched, state shifts to wrong rows"]
กฎ: key ต้อง คงที่ ไม่ซ้ำในกลุ่ม sibling และผูกกับ identity ของข้อมูล (ปกติคือ database id) — อย่าใช้ array index สำหรับ list ที่เปลี่ยนได้ และอย่าใช้ Math.random() (ซึ่งเปลี่ยนทุก render และบังคับให้ remount ทั้งหมด)