Error Boundaries
component เดียว crash ไม่ควรทำทั้งแอปว่างเปล่า
หัวข้อที่มีชื่อว่า “component เดียว crash ไม่ควรทำทั้งแอปว่างเปล่า”ถ้า component throw ระหว่าง render React — โดยตั้งใจ — จะ unmount ทั้ง tree แทนที่จะแสดง UI ที่เสียหาย error boundary คือ component ที่จับ error แบบนั้นใน subtree ของตัวเอง เพื่อให้ความล้มเหลวใน widget เดียวแสดง fallback ตรงนั้น แทนที่จะทำทั้งหน้าล่ม
error boundary ยังต้องเขียนเป็น class component เพราะพึ่ง lifecycle method สองตัวที่ไม่มี hook เทียบเท่า:
import { Component } from "react";
class ErrorBoundary extends Component { state = { error: null };
// render phase: คืน state ใหม่เพื่อแสดง fallback ใน render ถัดไป static getDerivedStateFromError(error) { return { error }; }
// commit phase: side effect — log error ไปยัง reporting service ของคุณ componentDidCatch(error, info) { reportError(error, info.componentStack); }
render() { if (this.state.error) return this.props.fallback; return this.props.children; }}ใน production ส่วนใหญ่ใช้ library เล็ก ๆ ชื่อ react-error-boundary ที่ห่อของแบบนี้และเพิ่มกลไก reset — แต่กลไกข้างในก็คือด้านบนเป๊ะ ๆ
<ErrorBoundary fallback={<p>Something went wrong.</p>}> <Widget /> {/* ถ้า Widget throw ระหว่าง render, fallback จะโชว์ตรงนี้ */}</ErrorBoundary>จับอะไรได้ — และจับอะไรไม่ได้
หัวข้อที่มีชื่อว่า “จับอะไรได้ — และจับอะไรไม่ได้”ตรงนี้คนเข้าใจผิดบ่อย error boundary จับ error ที่เกิด ระหว่าง rendering (และใน lifecycle method กับ constructor) ของ component ที่อยู่ ใต้ boundary แต่ ไม่ จับ:
- event handler — error ใน
onClickเป็น JS error ปกติ; ใช้try/catch - asynchronous code — promise ที่ reject หรือ callback ของ
setTimeoutรันนอก render ของ React ดังนั้นไม่มี boundary ไหนเห็น - error ใน boundary เอง — boundary จับเฉพาะ error ที่อยู่ ใต้ ตัวเอง
- server-side rendering error — จัดการแยกต่างหาก
flowchart TB eb["Error Boundary"] --> child["child render"] child -->|throw ระหว่าง render| caught["จับได้ → แสดง fallback"] handler["onClick throw"] -.ไม่ถูกจับ.-> handler2["ใช้ try/catch"] async["promise reject / setTimeout"] -.ไม่ถูกจับ.-> async2["จัดการเอง"]
จับคู่กับ Suspense
หัวข้อที่มีชื่อว่า “จับคู่กับ Suspense”Suspense และ error boundary เสริมกัน: Suspense จัดการ state loading (child กำลังรอ) ส่วน error boundary จัดการ state failure (child throw — รวมถึง promise ที่ reject ซึ่งอ่านด้วย use()) รวมกันได้ทั้งสาม state ของ async UI แบบ declarative:
<ErrorBoundary fallback={<Error />}> <Suspense fallback={<Spinner />}> <Profile /> {/* loading → Spinner, error → Error, success → Profile */} </Suspense></ErrorBoundary>เมื่อ use() อ่าน promise ที่ reject React จะ propagate error นั้นไปยัง error boundary ที่ใกล้ที่สุด — ดังนั้น boundary เดียวกันที่จัดการ render crash ก็จัดการ data-fetch failure ด้วย