Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> ซอฟต์แวร์ >> เบราว์เซอร์

Deep Dive: React Cache, Time Slicing, และ Synchronous API Fetching

ปีนี้ดูเหมือนจะเป็น "ปีของ 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 ปัญหา:

  1. Prioritize Events: User Input > Data Fetching
  2. 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

  1. Component เรียก customFetch
  2. Cache ว่าง -> throw (Algebraic Effect)
  3. Fiber Parent catch -> Fetch จริง -> เก็บ Cache
  4. 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

Deep Dive: React Cache, Time Slicing, และ Synchronous API Fetching

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 ใหม่ๆ ด้วย