ไทย

เรียนรู้วิธีการใช้ React Error Boundaries เพื่อจัดการข้อผิดพลาดอย่างนุ่มนวล ป้องกันแอปพลิเคชันล่ม และปรับปรุงประสบการณ์ผู้ใช้ สำรวจแนวทางปฏิบัติที่ดีที่สุด เทคนิคขั้นสูง และตัวอย่างการใช้งานจริง

React Error Boundaries: คู่มือฉบับสมบูรณ์เพื่อการจัดการข้อผิดพลาดที่แข็งแกร่ง

ในโลกของการพัฒนาเว็บสมัยใหม่ ประสบการณ์ผู้ใช้ที่ราบรื่นและเชื่อถือได้คือสิ่งสำคัญสูงสุด ข้อผิดพลาดเพียงครั้งเดียวที่ไม่ได้รับการจัดการสามารถทำให้แอปพลิเคชัน React ทั้งหมดล่มได้ ทำให้ผู้ใช้รู้สึกหงุดหงิดและอาจสูญเสียข้อมูลอันมีค่าไป React Error Boundaries เป็นกลไกอันทรงพลังที่ช่วยจัดการข้อผิดพลาดเหล่านี้อย่างนุ่มนวล ป้องกันการล่มที่รุนแรง และมอบประสบการณ์ที่ยืดหยุ่นและเป็นมิตรต่อผู้ใช้มากขึ้น คู่มือนี้จะให้ภาพรวมที่ครอบคลุมเกี่ยวกับ React Error Boundaries ตั้งแต่จุดประสงค์ การนำไปใช้ แนวทางปฏิบัติที่ดีที่สุด ไปจนถึงเทคนิคขั้นสูง

React Error Boundaries คืออะไร?

Error Boundaries คือคอมโพเนนต์ของ React ที่ดักจับข้อผิดพลาด JavaScript ที่เกิดขึ้นที่ใดก็ได้ในโครงสร้างคอมโพเนนต์ลูก (child component tree) บันทึกข้อผิดพลาดเหล่านั้น และแสดง UI สำรอง (fallback UI) แทนที่โครงสร้างคอมโพเนนต์ที่ล่มไป มันทำหน้าที่เหมือนตาข่ายนิรภัย ป้องกันไม่ให้ข้อผิดพลาดในส่วนหนึ่งของแอปพลิเคชันทำให้ UI ทั้งหมดล่มลง Error Boundaries ถูกนำมาใช้ใน React 16 เพื่อแทนที่กลไกการจัดการข้อผิดพลาดแบบเดิมซึ่งมีความแข็งแกร่งน้อยกว่า

ลองนึกภาพว่า Error Boundaries เป็นเหมือนบล็อก `try...catch` สำหรับคอมโพเนนต์ React แต่ต่างจาก `try...catch` ตรงที่มันทำงานสำหรับคอมโพเนนต์โดยเฉพาะ ซึ่งเป็นวิธีการจัดการข้อผิดพลาดแบบประกาศ (declarative) และนำกลับมาใช้ใหม่ได้ทั่วทั้งแอปพลิเคชันของคุณ

ทำไมต้องใช้ Error Boundaries?

Error Boundaries มีประโยชน์ที่สำคัญหลายประการ:

การสร้างคอมโพเนนต์ Error Boundary

ในการสร้างคอมโพเนนต์ Error Boundary คุณต้องกำหนดคลาสคอมโพเนนต์ (class component) ที่มีการใช้งานเมธอด lifecycle อย่างน้อยหนึ่งหรือทั้งสองอย่างต่อไปนี้:

นี่คือตัวอย่างพื้นฐานของคอมโพเนนต์ Error Boundary:


class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // อัปเดต state เพื่อให้การ render ครั้งถัดไปแสดง UI สำรอง
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // ตัวอย่าง "componentStack":
    //   in ComponentThatThrows (created by App)
    //   in App
    console.error("Caught an error: ", error, info.componentStack);
    // คุณยังสามารถบันทึกข้อผิดพลาดไปยังบริการรายงานข้อผิดพลาดได้
    // logErrorToMyService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // คุณสามารถ render UI สำรองที่กำหนดเองได้
      return 

Something went wrong.

; } return this.props.children; } }

คำอธิบาย:

การใช้งาน Error Boundaries

ในการใช้ Error Boundary เพียงแค่ครอบคอมโพเนนต์ที่คุณต้องการป้องกันด้วยคอมโพเนนต์ ErrorBoundary:



  


หาก ComponentThatMightThrow เกิดข้อผิดพลาด ErrorBoundary จะดักจับข้อผิดพลาด อัปเดต state ของมัน และ render UI สำรอง ส่วนที่เหลือของแอปพลิเคชันจะยังคงทำงานได้ตามปกติ

การวางตำแหน่ง Error Boundary

การวางตำแหน่งของ Error Boundaries เป็นสิ่งสำคัญอย่างยิ่งสำหรับการจัดการข้อผิดพลาดที่มีประสิทธิภาพ ลองพิจารณากลยุทธ์เหล่านี้:

ตัวอย่าง:


function App() {
  return (
    
); }

ในตัวอย่างนี้ แต่ละส่วนหลักของแอปพลิเคชัน (Header, Sidebar, ContentArea, Footer) ถูกครอบด้วย Error Boundary ซึ่งช่วยให้แต่ละส่วนสามารถจัดการข้อผิดพลาดได้อย่างอิสระ ป้องกันไม่ให้ข้อผิดพลาดเดียวส่งผลกระทบต่อทั้งแอปพลิเคชัน

การปรับแต่ง Fallback UI

UI สำรองที่แสดงโดย Error Boundary ควรให้ข้อมูลที่เป็นประโยชน์และเป็นมิตรต่อผู้ใช้ ลองพิจารณาแนวทางเหล่านี้:

ตัวอย่าง:


class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // อัปเดต state เพื่อให้การ render ครั้งถัดไปแสดง UI สำรอง
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // คุณยังสามารถบันทึกข้อผิดพลาดไปยังบริการรายงานข้อผิดพลาดได้
    console.error("Caught an error: ", error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // คุณสามารถ render UI สำรองที่กำหนดเองได้
      return (
        

อุ๊ปส์! มีบางอย่างผิดปกติ

ขออภัย เกิดข้อผิดพลาดขณะพยายามแสดงเนื้อหานี้

โปรดลองรีเฟรชหน้าเว็บหรือติดต่อฝ่ายสนับสนุนหากปัญหายังคงอยู่

ติดต่อฝ่ายสนับสนุน
); } return this.props.children; } }

ตัวอย่างนี้แสดง UI สำรองที่ให้ข้อมูลมากขึ้น ซึ่งรวมถึงข้อความแสดงข้อผิดพลาดที่ชัดเจน คำแนะนำในการแก้ไขปัญหา และลิงก์สำหรับรีเฟรชหน้าและติดต่อฝ่ายสนับสนุน

การจัดการข้อผิดพลาดประเภทต่างๆ

Error Boundaries จะดักจับข้อผิดพลาดที่เกิดขึ้นระหว่างการ render ในเมธอด lifecycle และใน constructor ของโครงสร้างทั้งหมดที่อยู่ภายใต้มัน แต่ *จะไม่* ดักจับข้อผิดพลาดสำหรับ:

ในการจัดการข้อผิดพลาดประเภทเหล่านี้ คุณต้องใช้เทคนิคที่แตกต่างกันไป

Event Handlers

สำหรับข้อผิดพลาดที่เกิดขึ้นใน event handler ให้ใช้บล็อก try...catch มาตรฐาน:


function MyComponent() {
  const handleClick = () => {
    try {
      // โค้ดที่อาจทำให้เกิดข้อผิดพลาด
      throw new Error("Something went wrong in the event handler");
    } catch (error) {
      console.error("Error in event handler: ", error);
      // จัดการข้อผิดพลาด (เช่น แสดงข้อความแสดงข้อผิดพลาด)
      alert("เกิดข้อผิดพลาด โปรดลองอีกครั้ง");
    }
  };

  return ;
}

โค้ดแบบอะซิงโครนัส

สำหรับข้อผิดพลาดที่เกิดขึ้นในโค้ดแบบอะซิงโครนัส ให้ใช้บล็อก try...catch ภายในฟังก์ชันอะซิงโครนัส:


function MyComponent() {
  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch("https://api.example.com/data");
        const data = await response.json();
        // ประมวลผลข้อมูล
        console.log(data);
      } catch (error) {
        console.error("Error fetching data: ", error);
        // จัดการข้อผิดพลาด (เช่น แสดงข้อความแสดงข้อผิดพลาด)
        alert("ไม่สามารถดึงข้อมูลได้ โปรดลองอีกครั้งในภายหลัง");
      }
    }

    fetchData();
  }, []);

  return 
กำลังโหลดข้อมูล...
; }

หรืออีกทางเลือกหนึ่ง คุณสามารถใช้กลไกการจัดการข้อผิดพลาดแบบโกลบอลสำหรับ unhandled promise rejections:


window.addEventListener('unhandledrejection', function(event) {
  console.error('Unhandled rejection (promise: ', event.promise, ', reason: ', event.reason, ');');
  // สามารถเลือกแสดงข้อความแสดงข้อผิดพลาดแบบโกลบอลหรือบันทึกข้อผิดพลาดไปยังบริการได้
  alert("เกิดข้อผิดพลาดที่ไม่คาดคิด โปรดลองอีกครั้งในภายหลัง");
});

เทคนิคขั้นสูงสำหรับ Error Boundary

การรีเซ็ต Error Boundary

ในบางกรณี คุณอาจต้องการให้ผู้ใช้สามารถรีเซ็ต Error Boundary และลองดำเนินการที่ทำให้เกิดข้อผิดพลาดอีกครั้ง ซึ่งจะมีประโยชน์หากข้อผิดพลาดเกิดจากปัญหาชั่วคราว เช่น ปัญหาเครือข่าย

ในการรีเซ็ต Error Boundary คุณสามารถใช้ไลบรารีการจัดการ state เช่น Redux หรือ Context เพื่อจัดการ state ของข้อผิดพลาดและจัดเตรียมฟังก์ชันสำหรับรีเซ็ต หรือคุณสามารถใช้วิธีที่ง่ายกว่าโดยการบังคับให้ Error Boundary ทำการ remount

ตัวอย่าง (การบังคับให้ Remount):


class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, errorCount: 0, key: 0 };
  }

  static getDerivedStateFromError(error) {
    // อัปเดต state เพื่อให้การ render ครั้งถัดไปแสดง UI สำรอง
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // คุณยังสามารถบันทึกข้อผิดพลาดไปยังบริการรายงานข้อผิดพลาดได้
    console.error("Caught an error: ", error, info.componentStack);
    this.setState(prevState => ({ errorCount: prevState.errorCount + 1 }));
  }

  resetError = () => {
      this.setState({hasError: false, key: this.state.key + 1})
  }

  render() {
    if (this.state.hasError) {
      // คุณสามารถ render UI สำรองที่กำหนดเองได้
      return (
        

อุ๊ปส์! มีบางอย่างผิดปกติ

ขออภัย เกิดข้อผิดพลาดขณะพยายามแสดงเนื้อหานี้

); } return
{this.props.children}
; } }

ในตัวอย่างนี้ มีการเพิ่ม 'key' เข้าไปใน div ที่ครอบอยู่ การเปลี่ยนค่า key จะบังคับให้คอมโพเนนต์ทำการ remount ซึ่งเป็นการล้าง state ของข้อผิดพลาดอย่างมีประสิทธิภาพ เมธอด `resetError` จะอัปเดต state `key` ของคอมโพเนนต์ ทำให้คอมโพเนนต์ remount และ re-render children ของมันอีกครั้ง

การใช้ Error Boundaries ร่วมกับ Suspense

React Suspense ช่วยให้คุณสามารถ "พัก" (suspend) การ render ของคอมโพเนนต์จนกว่าเงื่อนไขบางอย่างจะสำเร็จ (เช่น ดึงข้อมูลเสร็จสิ้น) คุณสามารถรวม Error Boundaries เข้ากับ Suspense เพื่อมอบประสบการณ์การจัดการข้อผิดพลาดที่แข็งแกร่งยิ่งขึ้นสำหรับการดำเนินการแบบอะซิงโครนัส


import React, { Suspense } from 'react';

function MyComponent() {
  return (
    
      กำลังโหลด...
}> ); } function DataFetchingComponent() { const data = useData(); // Custom hook ที่ดึงข้อมูลแบบอะซิงโครนัส return
{data.value}
; }

ในตัวอย่างนี้ DataFetchingComponent จะดึงข้อมูลแบบอะซิงโครนัสโดยใช้ custom hook คอมโพเนนต์ Suspense จะแสดงตัวบ่งชี้การโหลดในขณะที่กำลังดึงข้อมูล หากเกิดข้อผิดพลาดระหว่างกระบวนการดึงข้อมูล ErrorBoundary จะดักจับข้อผิดพลาดและแสดง UI สำรอง

แนวทางปฏิบัติที่ดีที่สุดสำหรับ React Error Boundaries

ตัวอย่างการใช้งานจริง

นี่คือตัวอย่างการใช้งานจริงบางส่วนของ Error Boundaries:

ทางเลือกอื่นนอกเหนือจาก Error Boundaries

แม้ว่า Error Boundaries จะเป็นวิธีที่แนะนำในการจัดการข้อผิดพลาดใน React แต่ก็มีแนวทางทางเลือกอื่นๆ ที่คุณสามารถพิจารณาได้ อย่างไรก็ตาม โปรดทราบว่าทางเลือกเหล่านี้อาจไม่มีประสิทธิภาพเท่ากับ Error Boundaries ในการป้องกันการล่มของแอปพลิเคชันและการมอบประสบการณ์ผู้ใช้ที่ราบรื่น

ท้ายที่สุดแล้ว Error Boundaries มอบแนวทางการจัดการข้อผิดพลาดที่แข็งแกร่งและเป็นมาตรฐานใน React ทำให้เป็นตัวเลือกที่เหมาะสมที่สุดสำหรับกรณีการใช้งานส่วนใหญ่

สรุป

React Error Boundaries เป็นเครื่องมือที่จำเป็นสำหรับการสร้างแอปพลิเคชัน React ที่แข็งแกร่งและเป็นมิตรต่อผู้ใช้ ด้วยการดักจับข้อผิดพลาดและแสดง UI สำรอง พวกมันช่วยป้องกันการล่มของแอปพลิเคชัน ปรับปรุงประสบการณ์ผู้ใช้ และทำให้การดีบักข้อผิดพลาดง่ายขึ้น โดยการปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดที่ระบุไว้ในคู่มือนี้ คุณสามารถนำ Error Boundaries ไปใช้ในแอปพลิเคชันของคุณได้อย่างมีประสิทธิภาพและสร้างประสบการณ์ผู้ใช้ที่ยืดหยุ่นและเชื่อถือได้มากขึ้นสำหรับผู้ใช้ทั่วโลก

React Error Boundaries: คู่มือฉบับสมบูรณ์เพื่อการจัดการข้อผิดพลาดที่แข็งแกร่ง | MLOG