توسعه فرانت‌اند

Error Boundaries در React: مدیریت خطاهای UI

محدوده Error Boundary، تفاوت خطای شبکه و Handler، Fallback و Reset با پروژه کامل React و Vite و خطای نمایشی هنگام Render.

1404/11/14 3 دقیقه مطالعه 1,049 بازدید

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

پیش از افزودن Boundary، مسیرهای خطا را جدا کنید: داده نامعتبر، درخواست ناموفق، خطای رویداد کاربر و شکست Render مسئولیت‌های متفاوت دارند. پیام قابل فهم، ثبت فنی و امکان تلاش دوباره نیز سه نیاز جدا هستند. Error Boundary بخشی از طراحی پایداری رابط است؛ نبود اعتبارسنجی، قرارداد داده یا بررسی خطاهای شناخته‌شده را جبران نمی‌کند. همچنین نباید خطا را پنهان کند و نتیجه ناقص را موفق نشان دهد.

چه خطاهایی در محدوده Boundary هستند؟

Boundary خطاهای زمان Render و بعضی مراحل چرخه عمر فرزندان را می‌گیرد و رابط Fallback نمایش می‌دهد. خطای خود Boundary توسط همان Boundary گرفته نمی‌شود. خطای Event Handler و خطای بسیاری از Callbackهای غیرهم‌زمان نیز خودکار در این مسیر قرار نمی‌گیرد. مستندات رسمی React مرزها و استثناهای مربوط را توضیح می‌دهد. رفتار چارچوب و APIهای جدید را باید جدا از قاعده کلی بخوانید.

در Render سمت سرور نیز نباید فرض کنید Boundary کلاینت همان پوشش را فراهم می‌کند. چارچوب ممکن است قرارداد دیگری برای مسیر خطا داشته باشد. برای درخواست شبکه، وضعیت Error را در تابع دریافت و رابط مدیریت کنید؛ برای کلیک ناموفق، خطای همان عملیات را نشان دهید. قراردادن try/catch اطراف JSX در کامپوننت والد جای Boundary نمی‌گیرد، چون اجرای Render فرزند در مرحله دیگری توسط React انجام می‌شود.

دو مسئولیت اصلی در کلاس Boundary

در پیاده‌سازی مستقیم با Class، getDerivedStateFromError می‌تواند وضعیت را برای نمایش Fallback تغییر دهد و componentDidCatch برای گزارش جزئیات به کار رود. وظیفه اول تعیین رابط و وظیفه دوم مشاهده خطاست. در React API رسمی کلاس برای این کار وجود دارد؛ اگر کتابخانه کمکی انتخاب می‌کنید، قرارداد و نسخه آن را بخوانید. استفاده از Function Component معمولی بدون سازوکار مربوط، Error Boundary ایجاد نمی‌کند.

ثبت خطا باید با سیاست محرمانگی هماهنگ باشد. متن، Stack و Props ممکن است اطلاعات حساس داشته باشند. ارسال مستقیم همه داده‌ها به یک سرویس گزارش، تصمیم امن عمومی نیست. شناسه رخداد و زمینه حداقلی می‌توانند برای پیگیری مفید باشند. پیام کاربر نباید Stack فنی، Token یا محتوای خصوصی را نمایش دهد. Fallback خوب هم وضعیت را روشن می‌کند و هم مرز داده را حفظ می‌کند.

نمونه کامل برای فهم شکست و بازیابی

پوشه تازه بسازید و سه فایل زیر را با نام‌های package.json، index.html و main.jsx در ریشه آن قرار دهید. Node نسخه ۲۲.۱۲ به بالا و npm لازم است. npm install و سپس npm run dev را اجرا کنید و نشانی نمایش‌داده‌شده توسط Vite را باز کنید. دکمه «ایجاد خطای نمایشی» وضعیت را عوض می‌کند تا فرزند هنگام Render خطا بدهد. این خطا عمداً آموزشی است و عملیات بیرونی ندارد.

فایل package.json

{"type":"module","scripts":{"dev":"vite"},"dependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"devDependencies":{"vite":"^8.0.0"}}

فایل index.html

<!doctype html>
<html lang="fa" dir="rtl">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>نمونه React</title></head>
<body><div id="root"></div><script type="module" src="/main.jsx"></script></body>
</html>

فایل main.jsx

import React, { Component, useState } from 'react';
import { createRoot } from 'react-dom/client';
class Boundary extends Component {
  state = { failed: false };
  static getDerivedStateFromError() { return { failed: true }; }
  componentDidCatch(error, info) {
    console.error('Demo render error:', error.message, info.componentStack);
  }
  render() {
    if (this.state.failed) return <section role="alert">
      <h2>بخش نمایشی در دسترس نیست</h2>
      <p>این خطا برای آموزش ایجاد شد.</p>
      <button type="button" onClick={this.props.onReset}>بازگشت به حالت سالم</button>
    </section>;
    return this.props.children;
  }
}
function Content({ broken }) {
  if (broken) throw new Error('Intentional render failure');
  return <p>بخش نمایشی سالم است.</p>;
}
function App() {
  const [broken, setBroken] = useState(false);
  const [generation, setGeneration] = useState(0);
  function reset() { setBroken(false); setGeneration(value => value + 1); }
  return <main><h1>Error Boundary</h1>
    <button type="button" onClick={() => setBroken(true)}>ایجاد خطای نمایشی</button>
    <Boundary key={generation} onReset={reset}><Content broken={broken} /></Boundary>
  </main>;
}
createRoot(document.getElementById('root')).render(<App />);

دکمه خطا بیرون محدوده Boundary است تا پس از شکست همچنان ساختار والد باقی بماند. Fallback دکمه بازگشت دارد؛ این دکمه علت نمونه را برطرف می‌کند و با تغییر key یک Boundary تازه می‌سازد. فقط پاک‌کردن وضعیت خطا بدون اصلاح علت ممکن است همان شکست را دوباره ایجاد کند. نمونه از داده ثابت استفاده می‌کند و روش بازیابی هر برنامه واقعی ممکن است به دریافت داده یا تغییر مسیر نیاز داشته باشد.

محدوده را از تجربه کاربر انتخاب کنید

یک Boundary در ریشه می‌تواند آخرین پوشش باشد، ولی برای همه بخش‌ها کافی نیست. اگر کارت پیشنهاد جانبی خطا کند، شاید صفحه اصلی هنوز قابل استفاده باشد. برای بخش مستقل می‌توان مرز مستقل داشت. در مقابل، جداسازی افراطی هر عنصر می‌تواند رابطی تکه‌تکه بسازد که ارتباط بخش‌ها روشن نیست. مرز را با واحد کار و وابستگی داده هماهنگ کنید؛ هدف حفظ تجربه معتبر باقی‌مانده است.

فرض کنید داشبورد شامل فهرست اصلی و نمودار کمکی است. شکست نمودار می‌تواند پیام جایگزین همان قسمت داشته باشد، در حالی که فهرست ادامه می‌دهد. اما اگر هر دو از داده بنیادی نامعتبر استفاده می‌کنند، نشان‌دادن یکی به عنوان نتیجه صحیح شاید گمراه‌کننده باشد. Boundary درباره صحت تجاری تصمیم نمی‌گیرد. شما باید مشخص کنید کدام بخش بدون دیگری هنوز قابل اعتماد و قابل استفاده است.

Fallback پیام و اقدام مشخص می‌خواهد

پیام باید بگوید کدام بخش در دسترس نیست و کاربر چه کاری می‌تواند بکند. «مشکلی پیش آمد» برای یک صفحه پیچیده شاید کافی نباشد. لازم نیست علت فنی را حدس بزنید یا وعده زمان رفع بدهید. اگر تلاش دوباره بی‌فایده است، دکمه‌ای با همین نام فقط رفتار تکراری می‌سازد. مسیر امن مانند برگشت به بخش قبلی یا دریافت پشتیبانی می‌تواند مناسب‌تر باشد، اگر واقعاً موجود است.

نام دکمه، Focus و اعلام وضعیت نیز اهمیت دارند. اگر خطا بخشی از فرم را از بین برده، درباره داده واردشده صادق باشید. وعده «اطلاعات ذخیره شد» بدون ذخیره واقعی ندهید. برای اصول رابط، راهنمای حالت‌های خالی و جایگزین کمک می‌کند پیام و اقدام را از ظاهر جدا ببینید. Fallback صفحه خطا است و باید با محتوای واقعی همان بخش هماهنگ باشد.

خطای درخواست را در لایه مناسب نگه دارید

پاسخ ۴۰۴ یا ۵۰۰ API معمولاً یک وضعیت مورد انتظار در قرارداد شبکه است و باید به Error مناسب تبدیل و در رابط مدیریت شود. Fetch به تنهایی همه وضعیت‌های HTTP را Reject نمی‌کند. مقایسه Fetch و Axios این موضوع را توضیح می‌دهد. اگر کتابخانه داده قابلیت پرتاب خطا به Boundary دارد، آن را آگاهانه فعال کنید و راه Reset مربوط را نیز بشناسید.

در TanStack Query، کش، Retry و وضعیت درخواست مسئله‌های جدا از Boundary هستند. ترکیب آن‌ها نیاز به هماهنگی بازیابی دارد. درخواست ناموفق نباید بی‌پایان به Reset رابط و درخواست دوباره منجر شود. خطای دسترسی ممکن است نیاز به ورود یا تغییر مجوز داشته باشد، نه تلاش مجدد. علت و اقدام را از قرارداد واقعی محصول بگیرید، نه از یک Fallback عمومی.

رویداد و عملیات غیرهم‌زمان

اگر در دکمه ذخیره خطا رخ می‌دهد، همان Handler باید مسیر موفقیت و شکست را مدیریت کند. نتیجه ناموفق را به وضعیت مناسب تبدیل کنید و فقط پس از تأیید واقعی پیام موفقیت بدهید. استفاده از Boundary به عنوان جایگزین تمام catchها ممکن است بخشی از خطاها را بدون رسیدگی باقی بگذارد. در عملیات نوشتن، Retry می‌تواند اثر تکراری ایجاد کند؛ قرارداد سرور و Idempotency مهم‌اند.

در نمونه، دکمه مستقیم خطا پرتاب نمی‌کند؛ وضعیت را عوض می‌کند و فرزند در Render خطا می‌دهد. این تفاوت عمدی است تا نمونه با توضیح Boundary تطابق داشته باشد. اگر throw را فقط در Callback کلیک بگذارید، انتظار همان Fallback درست نیست. متن آموزش باید محل خطا و مرحله اجرا را روشن کند. ظاهر یک پیام مشابه به معنی سازوکار یکسان نیست.

نگهداری و گزارش درست

گزارش‌ها را طوری دسته‌بندی کنید که تکرار یک خطا، نسخه برنامه و بخش درگیر قابل فهم باشند. خطای Cancelشده یا ورودی مورد انتظار را با شکست Render مخلوط نکنید. حذف خطا از Console به معنی رفع علت نیست. برای تغییر واقعی، مرور و بررسی مناسب پروژه لازم است؛ این مقاله ادعای اجرای نمونه یا بررسی سایت شما ندارد. منطق نمونه برای مطالعه و استفاده آموزشی ارائه شده است.

در پایان، برای هر مرز سه سؤال بنویسید: چه چیزی را پوشش می‌دهد، کاربر چه می‌بیند و چگونه به وضعیت معتبر برمی‌گردد؟ اگر پاسخ سوم روشن نیست، دکمه Retry را بی‌دلیل اضافه نکنید. Boundary مناسب خطای یک بخش را قابل فهم و محدود می‌کند و فرصت گزارش و بازیابی می‌دهد؛ کیفیت آن از انتخاب محدوده و قرارداد بازیابی می‌آید، نه فقط وجود یک کلاس در پروژه.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!