ปีนี้ดูเหมือนจะเป็น "ปีของ React" Hooks (16.7-alpha.0) Time Slicing, Suspense — ฟีเจอร์ใหม่มากมาย
บทความนี้ ไม่ใช่ สอนใช้ฟีเจอร์ใหม่ แต่พยายามพิสูจน์ว่า "พวกมันถูกสร้างขึ้นอย่างไร" เพื่อเข้าใจสิ่งที่เราเล่นอยู่
ทำไมเขียนบทความนี้?
ฟีเจอร์ทดลองที่น่าประหลาดที่สุด: Async Operations ด้วย Synchronous API
const bulbasaur = ApiResource.read() // Synchronous?! จริงไหม?
react-cache library ทำให้ Async Operations ใช้ Synchronous API ได้ นี่คือจุดเริ่มต้นการเข้าใจ React ภายใน
ควบคุม Operation ของ JavaScript
Fiber Architecture ให้ React ควบคุม Task Execution ได้ สร้างขึ้นเพื่อแก้ 2 ปัญหา:
- Prioritize Events: User Input > Data Fetching
- Async Split React Computation: ไม่ Block Main Thread ระหว่าง Render ยาว
State Change ทุกอย่างมาจาก Async Operations: setTimeout, fetch, Event Listeners
Async จัดการผ่าน: Tasks (Micro/Macro/Render), Event Loop, Call Stack
User Input vs Fetch: ใครชนะ?
Jake Archibald อธิบาย: Microtasks (Promise) รันและ Flush ก่อน Macrotask ถัดไป (setTimeout)
แต่ React ทำให้ fetch resolve หลัง onChange (User Input) ได้อย่างไร? มันไม่ได้ Hack Event Loop โดยตรง
Fiber: Call Stack ที่เขียนด้วย JavaScript
Brandon Dail (React Rally): Fiber = JavaScript Call Stack แตแทน Frame (Function + Metadata) ด้วย Fiber (Component + Metadata)
- Call Stack: Native, ไม่เข้าถึงจาก JS ได้, Stack Function Calls
- Fiber: เขียนด้วย JS, Unit = Component, เข้าถึง/แก้ไขได้, ควบคุม React Internals เท่านั้น
Fiber ไม่ได้ Redefine JavaScript แต่ควบคุมว่า React Methods ไหนควรเรียก/ไม่เรียก ใน Context ใด (เช่น Interrupt Lifecycle Methods)
Algebraic Effects ใน JavaScript
React ควบคุม Component ได้ (Fiber) ขาดแค่กลไกบอก React ว่า "Component นี้มีอะไรเปลี่ยนไป" -> Algebraic Effects
Algebraic Effects: ส่งข้อมูลไป Parent Function (Dispatcher) Interrupt Function ปัจจุบัน ให้ Parent จัดการ แล้ว Resume กลับ
JavaScript ไม่มี Native -> React ใช้ try/catch จำลอง: throw ส่งข้อมูลขึ้นไป Parent catch รับ จัดการ Resume
ตัวอย่าง: Synchronous Fetch
// Component
const bulbasaur = customFetch('/pokemon/bulbasaur');
// customFetch Implementation
function customFetch(url) {
if (cache[url]) return cache[url];
throw { type: 'FETCH', url }; // Algebraic Effect: Throw to Fiber Parent
}
// Fiber Parent (Wrapper)
function FiberWrapper(Component) {
try {
return Component();
} catch (effect) {
if (effect.type === 'FETCH') {
fetch(effect.url).then(data => {
cache[effect.url] = data;
// Re-render Component -> ตอนนี้ Cache มีข้อมูล -> customFetch คืนค่า Sync ได้!
scheduleReRender(Component);
});
}
}
}
สรุป Flow
- Component เรียก
customFetch - Cache ว่าง ->
throw(Algebraic Effect) - Fiber Parent
catch-> Fetch จริง -> เก็บ Cache - Schedule Re-render -> Component รันอีกครั้ง -> Cache มีข้อมูล -> Return Sync!
render ถูกเรียก 2 ครั้ง: ครั้งแรก Throw (Pause), ครั้งที่สอง Get Data (Resume) — OK เพราะ Render เป็น Pure Function (No Side Effects)
React Phases (3 Phases)
ก่อน Fiber: setState → render → Compare VDOM → Update DOM (Inlined)
หลัง Fiber: แบ่ง 3 Phases
- Render Phase: Pure, Deterministic, No Side Effects, Interruptible (Pause ระหว่าง Component Calls)
- Pre-commit Phase: อ่าน DOM State (Scroll Position) Read-only
- Commit Phase: Update DOM จริง Not Interruptible

Time Slicing
React Pause ได้ใน Render Phase ระหว่าง Component Calls Resume ทีหลัง -> 60 FPS (~16.7ms/frame) Enqueue Modifications -> Commit ช่วงๆ
สรุป
Fiber + Algebraic Effects (via try/catch) + 3 Phases = Time Slicing + Suspense
React ทำ Workaround ปัญหา Browser: Event Prioritization, Concurrency ทีมอื่น (Google) กำลังเสนอ API ใหม่ๆ ด้วย