Error Boundary คือคอมโพเนนต์ React ที่ดักจับข้อผิดพลาด JavaScript ในโครงสร้างต้นไม้ของคอมโพเนนต์ย่อยและแสดงอินเทอร์เฟซสำรองแทนหน้าแสดงความผิดพลาด ในบริบทของ React Native มันป้องกันการขัดข้องทั้งหมดของแอปพลิเคชันระหว่างข้อผิดพลาดการเรนเดอร์ที่ไม่ร้ายแรง ตาม React Documentation, 2024 error boundary ดักจับข้อผิดพลาดในเมธอด render ฮุควงจรชีวิต และคอนสตรักเตอร์ของคอมโพเนนต์ย่อย ทำให้แอปพลิเคชันทำงานต่อไปได้ ใน React Native สิ่งนี้สำคัญอย่างยิ่งเนื่องจากแอปมือถือไม่สามารถโหลดซ้ำด้วย F5 ได้ — ผู้ใช้สูญเสียเซสชันทั้งหมด
ประเด็นสำคัญ
Error Boundary คือกลไกของ React สำหรับการลดประสิทธิภาพอย่างค่อยเป็นค่อยไประหว่างข้อผิดพลาดการเรนเดอร์ ถูกนำมาใช้ใน React 16 (2017) ในรูปแบบคอมโพเนนต์หุ้มที่ implement หนึ่งในสองเมธอดวงจรชีวิต: static getDerivedStateFromError หรือ componentDidCatch Error Boundary ช่วยให้แสดงข้อความที่มีความหมายแก่ผู้ใช้แทนหน้าจอสีขาวว่างเปล่าหรือการขัดข้องทั้งหมดของแอปพลิเคชัน
ก่อน React 16 ข้อผิดพลาดที่ไม่ถูกจัดการใดๆ ใน render จะทำให้เกิด การขัดข้องทั้งหมดของแอปพลิเคชัน พร้อมกับการถอดโครงสร้าง DOM ทั้งหมด ในแอปพลิเคชันเว็บ นั่นหมายถึงหน้าจอสีขาวว่างเปล่า ใน React Native — การขัดข้องทั้งหมดพร้อมการกลับไปยังหน้าจอหลัก ทีม React ได้นำ Error Boundary มาใช้เป็นอะนาล็อกของบล็อก catch สำหรับ UI แบบประกาศ โดยยืมแนวคิดจากวิธีการ “let it crash” ในภาษา Erlang
ใน React Native การไม่มี Error Boundary หมายถึง การขัดข้องทั้งหมดของแอปพลิเคชัน เมื่อเกิดข้อผิดพลาดการเรนเดอร์ใดๆ ผู้ใช้สูญเสียเซสชันปัจจุบันทั้งหมดโดยไม่มีตัวเลือกการกู้คืน Error Boundary ใน React Native มีความสำคัญอย่างยิ่งเนื่องจากแอปมือถือไม่สามารถโหลดซ้ำได้เหมือนหน้าเว็บ — เซสชันผู้ใช้สูญเสียอย่างถาวร และผู้ใช้ต้องเริ่มต้นใหม่ทั้งหมด
Error Boundary ทำงานที่ระดับต้นไม้ React เมื่อคอมโพเนนต์ย่อยโยนข้อผิดพลาดใน render หรือวงจรชีวิต React จะไม่ถอดโครงสร้างต้นไม้ทั้งหมด แต่จะส่งต่อการควบคุมไปยัง Error Boundary ที่ใกล้ที่สุดในลำดับชั้น Boundary เรียก getDerivedStateFromError ตั้งค่า state.hasError = true และเรนเดอร์อินเทอร์เฟซสำรองแทนสาขาคอมโพเนนต์ที่เสียหาย
import React, { Component, ErrorInfo, ReactNode } from "react"
interface Props {
children: ReactNode
fallback?: ReactNode
}
interface State {
hasError: boolean
error?: Error
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error("Caught by boundary:", error)
Crashlytics.recordException(error)
}
render() {
if (this.state.hasError) {
return this.props.fallback || <FallbackUI />
}
return this.props.children
}
}
getDerivedStateFromError ตั้งค่าสถานะสำหรับการเรนเดอร์อินเทอร์เฟซสำรอง — นี่คือเมธอดสแตติกที่ถูกเรียกในช่วงการเรนเดอร์ก่อนการยืนยันการเปลี่ยนแปลง componentDidCatch ทำงานในช่วงการยืนยันและมีไว้สำหรับเอฟเฟกต์ข้างเคียง: การบันทึก การส่งรายงานการขัดข้องไปยัง Crashlytics การวิเคราะห์ สองเมธอดนี้แยกความรับผิดชอบระหว่างการจัดการสถานะ UI และเอฟเฟกต์ข้างเคียง
ในการสร้าง Error Boundary คุณต้อง implement คอมโพเนนต์คลาสด้วยเมธอด getDerivedStateFromError และ/หรือ componentDidCatch คอมโพเนนต์ฟังก์ชันไม่สามารถเป็น Error Boundary ได้ — React รองรับฟังก์ชันนี้เฉพาะคอมโพเนนต์คลาสเท่านั้นเนื่องจากต้องเข้าถึงเมธอดวงจรชีวิต ไลบรารี react-error-boundary มี implementation พร้อมใช้งานด้วย API ที่ใช้ฮุกเพื่อความสะดวก
// ตัวอย่างการใช้งาน Error Boundary ใน React Native
import { ErrorBoundary } from "react-error-boundary"
const FallbackComponent = ({ error, resetError }: FallbackProps) => (
<View style={styles.container}>
<Text>เกิดข้อผิดพลาดบางอย่าง</Text>
<Text>{error.message}</Text>
<Button title="ลองอีกครั้ง" onPress={resetError} />
</View>
)
const App = () => (
<SafeAreaView>
<ErrorBoundary FallbackComponent={<FallbackComponent />}>
<UserProfile userId={"123"} />
</ErrorBoundary>
<BottomNavigation />
</SafeAreaView>
)
ระดับการหุ้ม — Error Boundary สามารถวางไว้ในระดับต่างๆ ของลำดับชั้น Boundary ระดับโลกหนึ่งตัวที่รากของแอปจะแสดงอินเทอร์เฟซสำรองเมื่อเกิดข้อผิดพลาดใดๆ แต่การนำทางจะยังคงอยู่หลาย Boundary ในระดับหน้าจอช่วยแยกข้อผิดพลาด: หากหน้าจอหนึ่งเสียหาย หน้าจออื่นๆ ยังคงทำงานอย่างอิสระ react-error-boundary ทำให้กลไกการรีเซ็ตง่ายขึ้นผ่านฮุก useErrorBoundary ช่วยให้รีเซ็ตสถานะโดยไม่ต้องโหลดซ้ำ สำหรับโปรเจกต์ทั่วไป โครงสร้าง Boundary สามระดับถือว่าเหมาะสมที่สุดสำหรับแอปพลิเคชัน React Native
หลังจากเกิดข้อผิดพลาด ผู้ใช้สามารถคลิกปุ่ม “ลองอีกครั้ง” ซึ่งรีเซ็ต hasError เป็น false และเรนเดอร์ต้นไม้ย่อยใหม่ กลไกการรีเซ็ต สำคัญสำหรับการฟื้นฟูการทำงานของแอปพลิเคชันโดยไม่ต้องโหลดซ้ำ ใน react-error-boundary ใช้ callback onReset ซึ่งสามารถล้างแคช ดึงข้อมูลอีกครั้ง หรืออัปเดตสถานะในระดับสูงขึ้นไปของต้นไม้
Error Boundary ไม่ดักจับ ข้อผิดพลาดแบบอะซิงโครนัส — ข้อผิดพลาดใน setTimeout, setInterval, Promise, async/await React ไม่สามารถสกัดกั้นข้อผิดพลาดภายนอกรอบการเรนเดอร์และฮุควงจรชีวิตเนื่องจากทำงานในบริบทการดำเนินการที่ต่างกัน สำหรับข้อผิดพลาดแบบอะซิงโครนัส จำเป็นต้องมี try-catch แยกต่างหากในตัวจัดการหรือตัวจัดการเหตุการณ์ unhandledrejection ทั่วโลก
ข้อผิดพลาดใน onClick, onChange และตัวจัดการเหตุการณ์อื่นๆ ไม่ถูกดักจับโดย Error Boundary เนื่องจากทำงานภายนอกการเรนเดอร์ของ React การจัดการข้อผิดพลาด สำหรับตัวจัดการเหตุการณ์ควรอยู่ภายในตัวจัดการเองโดยใช้ try-catch ไลบรารี react-error-boundary มีฮุก useErrorHandler สำหรับโยนข้อผิดพลาดจากตัวจัดการเหตุการณ์ไปยัง Boundary ที่ใกล้ที่สุด
Error Boundary ไม่ทำงานบน ฝั่งเซิร์ฟเวอร์ ใน Next.js หรือ Gatsby เมธอด getDerivedStateFromError และ componentDidCatch จะไม่ถูกเรียกระหว่าง SSR เนื่องจากเมธอดวงจรชีวิตมีให้ใช้เฉพาะในเบราว์เซอร์ สำหรับข้อผิดพลาดของเซิร์ฟเวอร์ จำเป็นต้องมีกลยุทธ์แยกต่างหาก: try-catch ใน getServerSideProps หน้าสำรอง error.js (Next.js 13+) หรือ middleware ทั่วโลก
ใน React Native Error Boundary ไม่ป้องกันการขัดข้องในระดับโมดูลเนทีฟ การขัดข้องระดับเนทีฟ (segfault, หน่วยความจำไม่เพียงพอ, ข้อยกเว้นเนทีฟ) เกิดขึ้นในระดับ Objective-C หรือ Java และไม่ถึงเลเยอร์ JavaScript สำหรับการขัดข้องระดับเนทีฟ จำเป็นต้องใช้ Crashlytics NDK (Android) หรือ KSCrash (iOS) Error Boundary ป้องกันเฉพาะเลเยอร์ JavaScript ของแอปพลิเคชัน React Native
วาง Error Boundary ที่ ขอบเขตของโมดูลเชิงตรรกะ: หนึ่ง Boundary ต่อหน้าจอ หนึ่งต่อวิดเจ็ตของบุคคลที่สาม หนึ่งต่อฟอร์มที่ซับซ้อน ซึ่งจะแยกข้อผิดพลาดและช่วยให้ผู้ใช้ทำงานต่อในส่วนอื่นๆ ของแอปพลิเคชันได้ ควรมี Boundary ระดับรากเสมอ — สำหรับข้อผิดพลาดร้ายแรงในคอมโพเนนต์นำทางทั่วไปหรือผู้ให้บริการ แต่ละ Boundary รับผิดชอบส่วนอินเทอร์เฟซของตนเองและไม่ส่งผลกระทบต่อคอมโพเนนต์ข้างเคียงเมื่อเกิดข้อผิดพลาด
ส่งข้อผิดพลาดไปยัง Crashlytics หรือ Sentry ผ่าน componentDidCatch เสมอ เพิ่มบริบท: ชื่อหน้าจอ userId เวอร์ชันแอป พารามิเตอร์การนำทาง ใน Sentry มี breadcrumbs — ลำดับการกระทำของผู้ใช้ที่นำไปสู่ข้อผิดพลาด สำหรับการวิเคราะห์ความถี่ของข้อผิดพลาดที่ไม่ร้ายแรง ให้ใช้แดชบอร์ด Crashlytics ที่จัดกลุ่มตามปัญหา
อย่าใช้ fallback เปล่า — สร้างอินเทอร์เฟซที่มีความหมาย ชุดที่แนะนำ สำหรับ React Native: ข้อความแสดงข้อผิดพลาด (เป็นมิตรกับผู้ใช้ ไม่ใช่เชิงเทคนิค) ปุ่ม “ลองอีกครั้ง” ลิงก์ไปยังฝ่ายสนับสนุนหรือแชท หลีกเลี่ยง View ที่ว่างเปล่า — ผู้ใช้จะคิดว่าแอปขัดข้องทั้งหมดและจะปิดแอป fallback ควรถูกรวมเข้ากับการออกแบบโดยรวมของแอป
ทดสอบ Error Boundary แต่ละตัวโดยใช้ React Testing Library หรือ React Native Testing Library สร้างคอมโพเนนต์ทริกเกอร์ที่โยนข้อผิดพลาดเมื่อเรนเดอร์และตรวจสอบว่าอินเทอร์เฟซสำรองแสดงขึ้นหรือไม่ สำหรับการทดสอบแบบบูรณาการ ให้ใช้ storybook กับสถานะ Error Boundary ต่างๆ: ปกติ, ข้อผิดพลาด, สถานะหลังรีเซ็ต การทดสอบ Boundary โดยอัตโนมัติช่วยให้แน่ใจว่าเมื่อคอมโพเนนต์เปลี่ยนแปลง อินเทอร์เฟซสำรองยังคงทำงานอย่างถูกต้องในโปรดักชัน ความครอบคลุมการทดสอบสำหรับแต่ละ Boundary ควรเป็นข้อกำหนดบังคับในการตรวจสอบโค้ดสำหรับโปรเจกต์ React Native
คำถามที่พบบ่อย
React implement Error Boundary ผ่านคอมโพเนนต์คลาสเท่านั้นเนื่องจากต้องเข้าถึงเมธอดวงจรชีวิต componentDidCatch และ getDerivedStateFromError คอมโพเนนต์ฟังก์ชันไม่มีเมธอดดังกล่าว ไลบรารี react-error-boundary มีคลาส wrapper พร้อมใช้งานด้วย API ที่ใช้ฮุกเพื่อความสะดวกในการใช้งาน
ผลกระทบน้อยมาก — Error Boundary เพิ่มการตรวจสอบสถานะทุกครั้งที่เรนเดอร์ต้นไม้ย่อย การเปรียบเทียบ state.hasError เป็นการดำเนินการ O(1) ที่มีความซับซ้อนคงที่ เมื่อไม่มีข้อผิดพลาด จะไม่มีโอเวอร์เฮด เฉพาะเมื่อเกิดข้อผิดพลาด Boundary จะทำการเรนเดอร์อินเทอร์เฟซสำรองเพิ่มเติม
ไม่ แค่ 2–3 ระดับก็เพียงพอ: Boundary ระดับราก หนึ่งตัวสำหรับแอปพลิเคชันทั้งหมด Boundary ระดับหน้าจอ หนึ่งตัวสำหรับแต่ละสาขาการนำทาง และ Boundary ระดับท้องถิ่น สำหรับวิดเจ็ตที่สำคัญ (ฟอร์มชำระเงิน, แผนที่, แชท) การใช้ Boundary มากเกินไปทำให้สถาปัตยกรรมซับซ้อนโดยไม่มีประโยชน์ที่สำคัญ
Error Boundary และ Suspense เป็นอิสระต่อกัน: Suspense ดักจับการโหลด (Promise ที่รอดำเนินการใน React 18+), Error Boundary ดักจับข้อผิดพลาดการเรนเดอร์ สามารถรวมกันได้: <ErrorBoundary><Suspense><Component /></Suspense></ErrorBoundary> Suspense ทำงานก่อนระหว่างการโหลด Error Boundary ทำงานเมื่อเกิดข้อผิดพลาดของคอมโพเนนต์ที่โหลด
try-catch ดักจับข้อผิดพลาดในโค้ดคำสั่งแบบซิงโครนัส แต่ไม่สามารถสกัดกั้นข้อผิดพลาดการเรนเดอร์ JSX Error Boundary ถูกออกแบบมาเฉพาะสำหรับ UI แบบประกาศ: มันสกัดกั้นข้อผิดพลาดใน render ฮุควงจรชีวิต และคอนสตรักเตอร์ของคอมโพเนนต์ย่อย ซึ่ง try-catch ไม่สามารถทำได้เนื่องจากลักษณะเฉพาะของการเรนเดอร์ React
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม