با تسلط بر مرزهای خطای React، برنامههایی مقاوم و کاربرپسند بسازید. بهترین شیوهها، تکنیکهای پیادهسازی و استراتژیهای پیشرفته مدیریت خطا را بیاموزید.
مرزهای خطای React: تکنیکهای مدیریت خطای زیبا برای برنامههای قوی
در دنیای پویای توسعه وب، ساخت برنامههای قوی و کاربرپسند از اهمیت بالایی برخوردار است. ریاکت، کتابخانه محبوب جاوا اسکریپت برای ساخت رابطهای کاربری، مکانیزم قدرتمندی برای مدیریت خطاها به شیوهای زیبا فراهم میکند: مرزهای خطا (Error Boundaries). این راهنمای جامع به مفهوم مرزهای خطا میپردازد و هدف، پیادهسازی و بهترین شیوههای ساخت برنامههای ریاکت مقاوم را بررسی میکند.
درک نیاز به مرزهای خطا
کامپوننتهای ریاکت، مانند هر کدی، مستعد خطا هستند. این خطاها میتوانند از منابع مختلفی ناشی شوند، از جمله:
- دادههای غیرمنتظره: کامپوننتها ممکن است دادهها را در فرمتی غیرمنتظره دریافت کنند که منجر به مشکلات رندرینگ میشود.
- خطاهای منطقی: باگها در منطق کامپوننت میتوانند باعث رفتار و خطاهای غیرمنتظره شوند.
- وابستگیهای خارجی: مشکلات مربوط به کتابخانهها یا APIهای خارجی میتوانند خطاها را به کامپوننتهای شما منتقل کنند.
بدون مدیریت خطای مناسب، یک خطا در یک کامپوننت ریاکت میتواند کل برنامه را از کار بیندازد و منجر به تجربه کاربری ضعیفی شود. مرزهای خطا راهی برای گرفتن این خطاها و جلوگیری از انتشار آنها در درخت کامپوننت فراهم میکنند و تضمین میکنند که برنامه حتی در صورت شکست کامپوننتهای جداگانه، عملکرد خود را حفظ کند.
مرزهای خطای React چه هستند؟
مرزهای خطا کامپوننتهای ریاکت هستند که خطاهای جاوا اسکریپت را در هر کجای درخت کامپوننت فرزند خود میگیرند، آن خطاها را ثبت میکنند و به جای درخت کامپوننتی که از کار افتاده است، یک UI جایگزین (fallback) نمایش میدهند. آنها مانند یک شبکه ایمنی عمل میکنند و از کرش کردن کل برنامه توسط خطاها جلوگیری میکنند.
ویژگیهای کلیدی مرزهای خطا:
- فقط کامپوننتهای کلاسی: مرزهای خطا باید به عنوان کامپوننتهای کلاسی پیادهسازی شوند. کامپوننتهای تابعی و هوکها نمیتوانند برای ایجاد مرزهای خطا استفاده شوند.
- متدهای چرخه حیات: آنها از متدهای چرخه حیات خاصی مانند
static getDerivedStateFromError()
وcomponentDidCatch()
برای مدیریت خطاها استفاده میکنند. - مدیریت خطای محلی: مرزهای خطا فقط خطاهای کامپوننتهای فرزند خود را میگیرند، نه خطاهای درون خودشان را.
پیادهسازی مرزهای خطا
بیایید فرآیند ایجاد یک کامپوننت مرز خطای پایه را مرور کنیم:
۱. ایجاد کامپوننت مرز خطا
ابتدا، یک کامپوننت کلاسی جدید، به عنوان مثال به نام ErrorBoundary
ایجاد کنید:
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false
};
}
static getDerivedStateFromError(error) {
// وضعیت را بهروزرسانی کنید تا رندر بعدی UI جایگزین را نشان دهد.
return {
hasError: true
};
}
componentDidCatch(error, errorInfo) {
// همچنین میتوانید خطا را در یک سرویس گزارشدهی خطا ثبت کنید
console.error("Caught error: ", error, errorInfo);
// مثال: logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// میتوانید هر UI جایگزین سفارشی را رندر کنید
return (
<div>
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
توضیح:
- سازنده (Constructor): وضعیت کامپوننت را با
hasError: false
مقداردهی اولیه میکند. static getDerivedStateFromError(error)
: این متد چرخه حیات پس از پرتاب شدن یک خطا توسط یک کامپوننت فرزند فراخوانی میشود. این متد خطا را به عنوان آرگومان دریافت میکند و به شما اجازه میدهد وضعیت کامپوننت را بهروزرسانی کنید. در اینجا، ماhasError
را بهtrue
تغییر میدهیم تا UI جایگزین فعال شود. این یک متدstatic
است، بنابراین نمیتوانید ازthis
در داخل تابع استفاده کنید.componentDidCatch(error, errorInfo)
: این متد چرخه حیات پس از پرتاب شدن خطا توسط یک کامپوننت فرزند فراخوانی میشود. این متد دو آرگومان دریافت میکند:error
: خطایی که پرتاب شده است.errorInfo
: یک شیء حاوی اطلاعاتی در مورد پشته کامپوننتی که خطا در آن رخ داده است. این برای دیباگ کردن بسیار ارزشمند است.
در این متد، میتوانید خطا را در سرویسی مانند Sentry، Rollbar یا یک راه حل ثبت لاگ سفارشی ثبت کنید. از تلاش برای رندر مجدد یا رفع مستقیم خطا در این تابع خودداری کنید؛ هدف اصلی آن ثبت مشکل است.
render()
: متد رندر وضعیتhasError
را بررسی میکند. اگرtrue
باشد، یک UI جایگزین (در این مورد، یک پیام خطای ساده) را رندر میکند. در غیر این صورت، فرزندان کامپوننت را رندر میکند.
۲. استفاده از مرز خطا
برای استفاده از مرز خطا، کافی است هر کامپوننتی را که ممکن است خطا ایجاد کند با کامپوننت ErrorBoundary
بپوشانید:
import ErrorBoundary from './ErrorBoundary';
function MyComponent() {
// این کامپوننت ممکن است خطا ایجاد کند
return (
<ErrorBoundary>
<PotentiallyBreakingComponent />
</ErrorBoundary>
);
}
export default MyComponent;
اگر PotentiallyBreakingComponent
خطایی ایجاد کند، ErrorBoundary
آن را گرفته، خطا را ثبت کرده و UI جایگزین را رندر میکند.
۳. مثالهای گویا با زمینه جهانی
یک برنامه تجارت الکترونیک را در نظر بگیرید که اطلاعات محصول را از یک سرور راه دور دریافت و نمایش میدهد. یک کامپوننت به نام ProductDisplay
مسئول رندر جزئیات محصول است. با این حال، سرور ممکن است گاهی اوقات دادههای غیرمنتظرهای را برگرداند که منجر به خطاهای رندرینگ میشود.
// ProductDisplay.js
import React from 'react';
function ProductDisplay({ product }) {
// شبیهسازی یک خطای احتمالی اگر product.price یک عدد نباشد
if (typeof product.price !== 'number') {
throw new Error('Invalid product price');
}
return (
<div>
<h2>{product.name}</h2>
<p>Price: {product.price}</p>
<img src={product.imageUrl} alt={product.name} />
</div>
);
}
export default ProductDisplay;
برای محافظت در برابر چنین خطاهایی، کامپوننت ProductDisplay
را با یک ErrorBoundary
بپوشانید:
// App.js
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import ProductDisplay from './ProductDisplay';
function App() {
const product = {
name: 'Example Product',
price: 'Not a Number', // دادههای عمداً نادرست
imageUrl: 'https://example.com/image.jpg'
};
return (
<div>
<ErrorBoundary>
<ProductDisplay product={product} />
</ErrorBoundary>
</div>
);
}
export default App;
در این سناریو، از آنجایی که product.price
عمداً به جای عدد به صورت رشته تنظیم شده است، کامپوننت ProductDisplay
یک خطا ایجاد میکند. ErrorBoundary
این خطا را میگیرد، از کرش کردن کل برنامه جلوگیری میکند و به جای کامپوننت خراب ProductDisplay
، UI جایگزین را نمایش میدهد.
۴. مرزهای خطا در برنامههای بینالمللی شده
هنگام ساخت برنامهها برای مخاطبان جهانی، پیامهای خطا باید برای ارائه تجربه کاربری بهتر، بومیسازی شوند. مرزهای خطا میتوانند در کنار کتابخانههای بینالمللیسازی (i18n) برای نمایش پیامهای خطای ترجمه شده استفاده شوند.
// ErrorBoundary.js (با پشتیبانی از i18n)
import React from 'react';
import { useTranslation } from 'react-i18next'; // با فرض اینکه از react-i18next استفاده میکنید
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
static getDerivedStateFromError(error) {
return {
hasError: true,
error: error,
};
}
componentDidCatch(error, errorInfo) {
console.error("Caught error: ", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
return (
<FallbackUI error={this.state.error} errorInfo={this.state.errorInfo}/>
);
}
return this.props.children;
}
}
const FallbackUI = ({error, errorInfo}) => {
const { t } = useTranslation();
return (
<div>
<h2>{t('error.title')}</h2>
<p>{t('error.message')}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{error && error.toString()}<br />
{errorInfo?.componentStack}
</details>
</div>
);
}
export default ErrorBoundary;
در این مثال، ما از react-i18next
برای ترجمه عنوان و پیام خطا در UI جایگزین استفاده میکنیم. توابع t('error.title')
و t('error.message')
ترجمههای مناسب را بر اساس زبان انتخاب شده توسط کاربر بازیابی میکنند.
۵. ملاحظات برای رندر سمت سرور (SSR)
هنگام استفاده از مرزهای خطا در برنامههای رندر شده در سمت سرور، مدیریت مناسب خطاها برای جلوگیری از کرش کردن سرور بسیار مهم است. مستندات ریاکت توصیه میکند که از مرزهای خطا برای بازیابی از خطاهای رندرینگ در سرور استفاده نکنید. به جای آن، خطاها را قبل از رندر کامپوننت مدیریت کنید، یا یک صفحه خطای استاتیک را در سرور رندر کنید.
بهترین شیوهها برای استفاده از مرزهای خطا
- کامپوننتهای کوچک را بپوشانید: کامپوننتهای جداگانه یا بخشهای کوچکی از برنامه خود را با مرزهای خطا بپوشانید. این کار از کرش کردن کل UI توسط یک خطای واحد جلوگیری میکند. به جای کل برنامه، پوشاندن ویژگیها یا ماژولهای خاص را در نظر بگیرید.
- خطاها را ثبت کنید: از متد
componentDidCatch()
برای ثبت خطاها در یک سرویس نظارت استفاده کنید. این به شما کمک میکند تا مشکلات برنامه خود را ردیابی و رفع کنید. سرویسهایی مانند Sentry، Rollbar و Bugsnag گزینههای محبوبی برای ردیابی و گزارش خطا هستند. - UI جایگزین آموزنده ارائه دهید: یک پیام خطای کاربرپسند در UI جایگزین نمایش دهید. از اصطلاحات فنی خودداری کنید و دستورالعملهایی برای ادامه کار ارائه دهید (مثلاً، تازهسازی صفحه، تماس با پشتیبانی). در صورت امکان، اقدامات جایگزینی را که کاربر میتواند انجام دهد، پیشنهاد دهید.
- استفاده بیش از حد نکنید: از پوشاندن تک تک کامپوننتها با مرز خطا خودداری کنید. بر روی مناطقی تمرکز کنید که احتمال وقوع خطا در آنها بیشتر است، مانند کامپوننتهایی که دادهها را از APIهای خارجی دریافت میکنند یا تعاملات پیچیده کاربر را مدیریت میکنند.
- مرزهای خطا را تست کنید: با ایجاد عمدی خطا در کامپوننتهایی که مرزهای خطا آنها را پوشاندهاند، از عملکرد صحیح آنها اطمینان حاصل کنید. تستهای واحد یا تستهای یکپارچهسازی بنویسید تا تأیید کنید که UI جایگزین به درستی نمایش داده میشود و خطاها به درستی ثبت میشوند.
- مرزهای خطا برای موارد زیر مناسب نیستند:
- مدیریتکنندههای رویداد (Event handlers)
- کد ناهمزمان (مانند کالبکهای
setTimeout
یاrequestAnimationFrame
) - رندر سمت سرور
- خطاهایی که در خود مرز خطا ایجاد میشوند (به جای فرزندان آن)
استراتژیهای پیشرفته مدیریت خطا
۱. مکانیزمهای تلاش مجدد
در برخی موارد، ممکن است با تلاش مجدد برای عملیاتی که باعث خطا شده است، بتوان از خطا بازیابی کرد. به عنوان مثال، اگر یک درخواست شبکه با شکست مواجه شود، میتوانید پس از یک تأخیر کوتاه دوباره آن را امتحان کنید. مرزهای خطا میتوانند با مکانیزمهای تلاش مجدد ترکیب شوند تا تجربه کاربری مقاومتری ارائه دهند.
// ErrorBoundaryWithRetry.js
import React from 'react';
class ErrorBoundaryWithRetry extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
retryCount: 0,
};
}
static getDerivedStateFromError(error) {
return {
hasError: true,
};
}
componentDidCatch(error, errorInfo) {
console.error("Caught error: ", error, errorInfo);
}
handleRetry = () => {
this.setState(prevState => ({
hasError: false,
retryCount: prevState.retryCount + 1,
}), () => {
// این کامپوننت را مجبور به رندر مجدد میکند. الگوهای بهتری را با propsهای کنترل شده در نظر بگیرید.
this.forceUpdate(); // هشدار: با احتیاط استفاده کنید
if (this.props.onRetry) {
this.props.onRetry();
}
});
};
render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong.</h2>
<button onClick={this.handleRetry}>Retry</button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundaryWithRetry;
کامپوننت ErrorBoundaryWithRetry
شامل یک دکمه تلاش مجدد است که با کلیک بر روی آن، وضعیت hasError
را بازنشانی کرده و کامپوننتهای فرزند را دوباره رندر میکند. همچنین میتوانید یک retryCount
اضافه کنید تا تعداد تلاشهای مجدد را محدود کنید. این رویکرد میتواند به ویژه برای مدیریت خطاهای گذرا، مانند قطعیهای موقت شبکه، مفید باشد. اطمینان حاصل کنید که prop `onRetry` به درستی مدیریت میشود و منطقی که ممکن است خطا داده باشد را مجدداً دریافت/اجرا میکند.
۲. فلگهای ویژگی (Feature Flags)
فلگهای ویژگی به شما این امکان را میدهند که ویژگیها را در برنامه خود به صورت پویا، بدون نیاز به استقرار کد جدید، فعال یا غیرفعال کنید. مرزهای خطا میتوانند در کنار فلگهای ویژگی برای تخریب زیبای عملکرد در صورت بروز خطا استفاده شوند. به عنوان مثال، اگر یک ویژگی خاص باعث خطا میشود، میتوانید آن را با استفاده از یک فلگ ویژگی غیرفعال کرده و پیامی به کاربر نمایش دهید که نشان میدهد این ویژگی به طور موقت در دسترس نیست.
۳. الگوی قطعکننده مدار (Circuit Breaker)
الگوی قطعکننده مدار یک الگوی طراحی نرمافزار است که برای جلوگیری از تلاش مکرر یک برنامه برای اجرای عملیاتی که احتمالاً با شکست مواجه میشود، استفاده میشود. این الگو با نظارت بر نرخ موفقیت و شکست یک عملیات کار میکند و اگر نرخ شکست از یک آستانه مشخص فراتر رود، "مدار را باز میکند" و از تلاشهای بعدی برای اجرای آن عملیات برای یک دوره زمانی مشخص جلوگیری میکند. این میتواند به جلوگیری از شکستهای زنجیرهای کمک کرده و پایداری کلی برنامه را بهبود بخشد.
مرزهای خطا میتوانند برای پیادهسازی الگوی قطعکننده مدار در برنامههای ریاکت استفاده شوند. هنگامی که یک مرز خطا، خطایی را میگیرد، میتواند یک شمارنده شکست را افزایش دهد. اگر شمارنده شکست از یک آستانه فراتر رود، مرز خطا میتواند پیامی به کاربر نمایش دهد که نشان میدهد ویژگی به طور موقت در دسترس نیست و از تلاشهای بعدی برای اجرای عملیات جلوگیری کند. پس از یک دوره زمانی مشخص، مرز خطا میتواند "مدار را ببندد" و اجازه دهد تلاشها برای اجرای دوباره عملیات از سر گرفته شوند.
نتیجهگیری
مرزهای خطای ریاکت ابزاری ضروری برای ساخت برنامههای قوی و کاربرپسند هستند. با پیادهسازی مرزهای خطا، میتوانید از کرش کردن کل برنامه توسط خطاها جلوگیری کنید، یک UI جایگزین زیبا به کاربران خود ارائه دهید و خطاها را برای اشکالزدایی و تجزیه و تحلیل در سرویسهای نظارت ثبت کنید. با پیروی از بهترین شیوهها و استراتژیهای پیشرفته ذکر شده در این راهنما، میتوانید برنامههای ریاکتی بسازید که مقاوم، قابل اعتماد و ارائهدهنده تجربه کاربری مثبت باشند، حتی در مواجهه با خطاهای غیرمنتظره. به یاد داشته باشید که بر ارائه پیامهای خطای مفید که برای مخاطبان جهانی بومیسازی شدهاند، تمرکز کنید.