Error Boundary: محدود کردن اثر یک خطا به بخشی از برنامه

در درس قبل دیدیم که رفتار پیش‌فرض React در برابر یک خطای Render، Unmount کردن کل درخت است. Error Boundary، ابزاری است که React برای کنترل همین رفتار ارائه می‌دهد: یک مرز مشخص که خطای رخ‌داده در هر جایی داخل آن را می‌گیرد و به‌جای نابود کردن کل برنامه، یک رابط کاربری جایگزین نشان می‌دهد.

ایده اصلی: یک مرز اطراف بخشی از درخت

Error Boundary، یک کامپوننت خاص است که دور بخشی از درخت کامپوننت‌ها قرار می‌گیرد. اگر هر کامپوننتی در داخل این مرز، در حین Render خطا پرتاب کند، React به‌جای Unmount کردن کل برنامه، فقط همان بخش داخل مرز را با یک رابط کاربری جایگزین (Fallback UI) عوض می‌کند؛ بقیه برنامه، بیرون از این مرز، دست‌نخورده باقی می‌ماند.

چرا Error Boundary فقط با Class Component نوشته می‌شود

نکته مهمی که باید از همین ابتدا بدانیم: در زمان نگارش این درس، React هنوز هیچ Hook معادلی برای ساخت Error Boundary ارائه نداده و این یکی از معدود مواردی است که همچنان به Class Component نیاز دارد؛ همان‌طور که در فصل پنجم اشاره کردیم، Class Components مسیر اصلی آموزش این دوره نیستند، اما اینجا یک استثنای ضروری و Legacy هستند.

jsx
import { Component } from "react";

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

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <p>مشکلی پیش آمد.</p>;
    }
    return this.props.children;
  }
}

متد خاص getDerivedStateFromError، وقتی یکی از فرزندان این کامپوننت در حین Render خطا بیندازد، فراخوانی می‌شود و مقدار بازگشتی آن، State جدید همین Error Boundary را می‌سازد.

استفاده از Error Boundary دور یک بخش

jsx
function Dashboard() {
  return (
    <div>
      <Header />
      <ErrorBoundary>
        <WeatherWidget />
      </ErrorBoundary>
      <Footer />
    </div>
  );
}

حالا اگر WeatherWidget در حین Render خطا بیندازد، فقط همان بخش با پیام «مشکلی پیش آمد» جایگزین می‌شود؛ Header و Footer، چون بیرون از این مرز قرار دارند، بدون هیچ مشکلی به کار خودشان ادامه می‌دهند.

چند مرز، چند نقطه شکست مستقل

می‌توان چند Error Boundary جداگانه، دور بخش‌های مختلف صفحه قرار داد تا هرکدام مستقل از دیگری خراب شوند. این الگو، شبیه همان اصل مسئولیت واحد کامپوننت‌هاست که در فصل دوم دیدیم؛ با این تفاوت که اینجا موضوع تحمل خطا (Fault Isolation) است، نه فقط سازمان‌دهی کد.

چه چیزی Error Boundary نمی‌گیرد

Error Boundary فقط خطاهایی را می‌گیرد که در حین Render، در متدهای چرخه حیات، یا در Constructor کامپوننت‌های فرزندش رخ می‌دهند. خطاهای داخل Event Handlerها (مثل یک onClick) یا خطاهای داخل عملیات غیرهمزمان (مثل یک Promise در useEffect) را نمی‌گیرد؛ چون آن‌ها اصلاً در جریان Render رخ نمی‌دهند. مدیریت آن نوع خطاها، موضوع درس چهارم همین فصل است.

مثال قابل اجرا

در این مثال، بخش خراب داخل یک Error Boundary قرار گرفته؛ با رخ دادن خطا، فقط همان بخش جایگزین می‌شود، نه کل صفحه.

محدود کردن اثر یک خطا با Error Boundary
import { Component, useState } from "react";

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

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <p>این بخش با مشکل مواجه شد.</p>;
    }
    return this.props.children;
  }
}

function BrokenWidget({ user }) {
  return <p>نام: {user.profile.name}</p>;
}

function App() {
  const [showBroken, setShowBroken] = useState(false);

  return (
    <div>
      <p>این بخش همیشه سالم باقی می‌ماند.</p>
      <button onClick={() => setShowBroken(true)}>نمایش ویجت خراب</button>
      <ErrorBoundary>
        {showBroken && <BrokenWidget user={{}} />}
      </ErrorBoundary>
    </div>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

Error Boundary یک مرز است که خطای Render هر فرزند داخل خودش را می‌گیرد و با یک رابط جایگزین نشان می‌دهد، بدون این‌که بقیه برنامه را از بین ببرد؛ در حال حاضر فقط با Class Component قابل نوشتن است. این ابزار فقط خطاهای Render را می‌گیرد، نه خطاهای داخل Event Handler یا کد غیرهمزمان. در درس بعدی، به طراحی خود Fallback UI می‌پردازیم.