راهنمای جامع برای درک و پیادهسازی مرزهای خطای جاوا اسکریپت (Error Boundaries) در React برای مدیریت قوی خطا و تخریب زیبای رابط کاربری.
مرز خطای جاوا اسکریپت (Error Boundary): راهنمای پیادهسازی مدیریت خطا در React
در دنیای توسعه React، خطاهای غیرمنتظره میتوانند منجر به تجربیات کاربری ناخوشایند و ناپایداری برنامه شوند. یک استراتژی مدیریت خطای خوب تعریفشده برای ساخت برنامههای قوی و قابل اعتماد حیاتی است. مرزهای خطای (Error Boundaries) ریاکت یک مکانیزم قدرتمند برای مدیریت زیبای خطاهایی که در درخت کامپوننت شما رخ میدهند، فراهم میکنند، از کرش کردن کل برنامه جلوگیری کرده و به شما اجازه میدهند یک UI جایگزین نمایش دهید.
مرز خطا (Error Boundary) چیست؟
یک مرز خطا کامپوننتی در React است که خطاهای جاوا اسکریپت را در هر جای درخت کامپوننت فرزند خود میگیرد، آن خطاها را ثبت میکند و به جای درخت کامپوننتی که کرش کرده، یک UI جایگزین نمایش میدهد. مرزهای خطا، خطاها را در حین رندرینگ، در متدهای چرخه حیات و در سازندههای کل درخت زیرمجموعه خود میگیرند.
یک مرز خطا را مانند یک بلوک try...catch
برای کامپوننتهای React در نظر بگیرید. همانطور که یک بلوک try...catch
به شما اجازه میدهد استثناها را در کد جاوا اسکریپت همزمان (synchronous) مدیریت کنید، یک مرز خطا به شما اجازه میدهد خطاهایی را که در حین رندر شدن کامپوننتهای React شما رخ میدهند، مدیریت کنید.
نکته مهم: مرزهای خطا خطاهای مربوط به موارد زیر را نمیگیرند:
- مدیریتکنندههای رویداد (Event handlers) (در بخشهای بعدی بیشتر بیاموزید)
- کد ناهمزمان (Asynchronous) (مانند کالبکهای
setTimeout
یاrequestAnimationFrame
) - رندر سمت سرور (Server-side rendering)
- خطاهایی که در خود مرز خطا پرتاب میشوند (به جای فرزندانش)
چرا از مرزهای خطا استفاده کنیم؟
استفاده از مرزهای خطا چندین مزیت قابل توجه دارد:
- تجربه کاربری بهبودیافته: به جای نمایش یک صفحه سفید خالی یا یک پیام خطای رمزآلود، میتوانید یک UI جایگزین کاربرپسند نمایش دهید که به کاربر اطلاع میدهد مشکلی پیش آمده و احتمالاً راهی برای بازیابی ارائه میدهد (مانند بارگذاری مجدد صفحه یا رفتن به بخش دیگری).
- پایداری برنامه: مرزهای خطا از کرش کردن کل برنامه به دلیل خطاهای یک بخش از آن جلوگیری میکنند. این موضوع به ویژه برای برنامههای پیچیده با کامپوننتهای به هم پیوسته بسیار مهم است.
- مدیریت متمرکز خطا: مرزهای خطا یک مکان متمرکز برای ثبت خطاها و ردیابی علت اصلی مشکلات فراهم میکنند. این کار دیباگ کردن و نگهداری را سادهتر میکند.
- تخریب زیبا (Graceful Degradation): شما میتوانید به صورت استراتژیک مرزهای خطا را در اطراف بخشهای مختلف برنامه خود قرار دهید تا اطمینان حاصل کنید که حتی اگر برخی از کامپوننتها از کار بیفتند، بقیه برنامه همچنان کاربردی باقی میماند. این امکان تخریب زیبا در مواجهه با خطاها را فراهم میکند.
پیادهسازی مرزهای خطا در React
برای ایجاد یک مرز خطا، باید یک کامپوننت کلاسی تعریف کنید که یکی (یا هر دو) از متدهای چرخه حیات زیر را پیادهسازی کند:
static getDerivedStateFromError(error)
: این متد چرخه حیات پس از پرتاب شدن یک خطا توسط یک کامپوننت فرزند فراخوانی میشود. این متد خطای پرتاب شده را به عنوان آرگومان دریافت میکند و باید مقداری را برای بهروزرسانی state کامپوننت برگرداند تا نشان دهد که خطایی رخ داده است (مثلاً تنظیم یک فلگhasError
بهtrue
).componentDidCatch(error, info)
: این متد چرخه حیات پس از پرتاب شدن یک خطا توسط یک کامپوننت فرزند فراخوانی میشود. این متد خطای پرتاب شده را به عنوان آرگومان، به همراه یک شیءinfo
حاوی اطلاعاتی در مورد اینکه کدام کامپوننت خطا را پرتاب کرده است، دریافت میکند. میتوانید از این متد برای ثبت خطا در سرویسی مانند Sentry یا Bugsnag استفاده کنید.
در اینجا یک مثال ساده از یک کامپوننت مرز خطا آورده شده است:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error) {
// بهروزرسانی state تا رندر بعدی، UI جایگزین را نمایش دهد.
return {
hasError: true,
error: error
};
}
componentDidCatch(error, info) {
// نمونه "componentStack":
// in ComponentThatThrows (created by App)
// in MyErrorBoundary (created by App)
// in div (created by App)
// in App
console.error("Caught an error:", error, info);
this.setState({
errorInfo: info.componentStack
});
// همچنین میتوانید خطا را در یک سرویس گزارشدهی خطا ثبت کنید
//logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// میتوانید هر UI جایگزین سفارشی را رندر کنید
return (
<div>
<h2>Something went wrong.</h2>
<p>Error: {this.state.error ? this.state.error.message : "An unknown error occurred."}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.errorInfo && this.state.errorInfo}
</details>
</div>
);
}
return this.props.children;
}
}
برای استفاده از مرز خطا، کافی است درخت کامپوننتی را که میخواهید محافظت کنید، با آن بپوشانید:
<ErrorBoundary>
<MyComponentThatMightThrow/>
</ErrorBoundary>
مثالهای عملی از کاربرد مرز خطا
بیایید چند سناریوی عملی را بررسی کنیم که در آنها مرزهای خطا میتوانند به ویژه مفید باشند:
۱. مدیریت خطاهای API
هنگام دریافت داده از یک API، ممکن است به دلیل مشکلات شبکه، مشکلات سرور یا دادههای نامعتبر، خطا رخ دهد. میتوانید کامپوننتی که دادهها را دریافت و نمایش میدهد را با یک مرز خطا بپوشانید تا این خطاها را به زیبایی مدیریت کنید.
function UserProfile() {
const [user, setUser] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(true);
React.useEffect(() => {
async function fetchData() {
try {
const response = await fetch('/api/user');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (error) {
// خطا توسط ErrorBoundary گرفته خواهد شد
throw error;
} finally {
setIsLoading(false);
}
}
fetchData();
}, []);
if (isLoading) {
return <p>Loading user profile...</p>;
}
if (!user) {
return <p>No user data available.</p>;
}
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
function App() {
return (
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
);
}
در این مثال، اگر فراخوانی API با شکست مواجه شود یا خطا برگرداند، مرز خطا، خطا را گرفته و یک UI جایگزین (که در متد render
مرز خطا تعریف شده) نمایش میدهد. این کار از کرش کردن کل برنامه جلوگیری کرده و پیام آموزندهتری به کاربر ارائه میدهد. شما میتوانید UI جایگزین را گسترش دهید تا گزینهای برای تلاش مجدد درخواست فراهم کند.
۲. مدیریت خطاهای کتابخانههای شخص ثالث
هنگام استفاده از کتابخانههای شخص ثالث، ممکن است آنها خطاهای غیرمنتظرهای پرتاب کنند. پوشاندن کامپوننتهایی که از این کتابخانهها استفاده میکنند با مرزهای خطا میتواند به شما کمک کند این خطاها را به زیبایی مدیریت کنید.
یک کتابخانه نمودارسازی فرضی را در نظر بگیرید که گاهی به دلیل ناهماهنگی دادهها یا مشکلات دیگر خطا پرتاب میکند. شما میتوانید کامپوننت نمودار را به این صورت بپوشانید:
function MyChartComponent() {
try {
// رندر کردن نمودار با استفاده از کتابخانه شخص ثالث
return <Chart data={data} />;
} catch (error) {
// این بلوک catch برای خطاهای چرخه حیات کامپوننت React مؤثر نخواهد بود
// این عمدتاً برای خطاهای همزمان در این تابع خاص است.
console.error("Error rendering chart:", error);
// پرتاب خطا برای گرفته شدن توسط ErrorBoundary را در نظر بگیرید
throw error; // پرتاب مجدد خطا
}
}
function App() {
return (
<ErrorBoundary>
<MyChartComponent />
</ErrorBoundary>
);
}
اگر کامپوننت Chart
خطایی پرتاب کند، مرز خطا آن را گرفته و یک UI جایگزین نمایش میدهد. توجه داشته باشید که try/catch درون MyChartComponent فقط خطاهای درون تابع همزمان را میگیرد، نه خطاهای چرخه حیات کامپوننت را. بنابراین، ErrorBoundary در اینجا حیاتی است.
۳. مدیریت خطاهای رندرینگ
خطاها میتوانند در طول فرآیند رندرینگ به دلیل دادههای نامعتبر، نوع propهای نادرست یا مشکلات دیگر رخ دهند. مرزهای خطا میتوانند این خطاها را گرفته و از کرش کردن برنامه جلوگیری کنند.
function DisplayName({ name }) {
if (typeof name !== 'string') {
throw new Error('Name must be a string');
}
return <h2>Hello, {name}!</h2>;
}
function App() {
return (
<ErrorBoundary>
<DisplayName name={123} /> <!-- نوع prop نادرست است -->
</ErrorBoundary>
);
}
در این مثال، کامپوننت DisplayName
انتظار دارد که prop name
یک رشته باشد. اگر به جای آن یک عدد ارسال شود، خطایی پرتاب شده و مرز خطا آن را گرفته و یک UI جایگزین نمایش میدهد.
مرزهای خطا و مدیریتکنندههای رویداد (Event Handlers)
همانطور که قبلاً ذکر شد، مرزهای خطا خطاهایی را که در مدیریتکنندههای رویداد رخ میدهند، نمیگیرند. این به این دلیل است که مدیریتکنندههای رویداد معمولاً ناهمزمان هستند و مرزهای خطا فقط خطاهایی را میگیرند که در حین رندرینگ، در متدهای چرخه حیات و در سازندهها رخ میدهند.
برای مدیریت خطاها در مدیریتکنندههای رویداد، باید از یک بلوک try...catch
سنتی در داخل تابع مدیریتکننده رویداد استفاده کنید.
function MyComponent() {
const handleClick = () => {
try {
// کدی که ممکن است خطا ایجاد کند
throw new Error('An error occurred in the event handler');
} catch (error) {
console.error('Caught an error in the event handler:', error);
// مدیریت خطا (مثلاً نمایش یک پیام خطا به کاربر)
}
};
return <button onClick={handleClick}>Click Me</button>;
}
مدیریت خطای سراسری
در حالی که مرزهای خطا برای مدیریت خطاها در درخت کامپوننت React عالی هستند، اما همه سناریوهای خطای ممکن را پوشش نمیدهند. به عنوان مثال، آنها خطاهایی را که خارج از کامپوننتهای React رخ میدهند، مانند خطاها در شنوندگان رویداد سراسری یا خطاها در کدی که قبل از مقداردهی اولیه React اجرا میشود، نمیگیرند.
برای مدیریت این نوع خطاها، میتوانید از مدیریتکننده رویداد window.onerror
استفاده کنید.
window.onerror = function(message, source, lineno, colno, error) {
console.error('Global error handler:', message, source, lineno, colno, error);
// ثبت خطا در سرویسی مانند Sentry یا Bugsnag
// نمایش پیام خطای سراسری به کاربر (اختیاری)
return true; // جلوگیری از رفتار پیشفرض مدیریت خطا
};
مدیریتکننده رویداد window.onerror
هر زمان که یک خطای جاوا اسکریپت گرفتهنشده رخ دهد، فراخوانی میشود. میتوانید از آن برای ثبت خطا، نمایش پیام خطای سراسری به کاربر یا انجام اقدامات دیگر برای مدیریت خطا استفاده کنید.
مهم: برگرداندن true
از مدیریتکننده رویداد window.onerror
از نمایش پیام خطای پیشفرض توسط مرورگر جلوگیری میکند. با این حال، به تجربه کاربری توجه داشته باشید؛ اگر پیام پیشفرض را سرکوب میکنید، اطمینان حاصل کنید که یک جایگزین واضح و آموزنده ارائه میدهید.
بهترین شیوهها برای استفاده از مرزهای خطا
در اینجا چند بهترین شیوه برای به خاطر سپردن هنگام استفاده از مرزهای خطا آورده شده است:
- مرزهای خطا را به صورت استراتژیک قرار دهید: بخشهای مختلف برنامه خود را با مرزهای خطا بپوشانید تا خطاها را جدا کرده و از آبشاری شدن آنها جلوگیری کنید. پوشاندن کل مسیرها یا بخشهای اصلی UI خود را در نظر بگیرید.
- UI جایگزین آموزنده ارائه دهید: UI جایگزین باید به کاربر اطلاع دهد که خطایی رخ داده و احتمالاً راهی برای بازیابی ارائه دهد. از نمایش پیامهای خطای عمومی مانند «مشکلی پیش آمد» خودداری کنید.
- خطاها را ثبت کنید: از متد چرخه حیات
componentDidCatch
برای ثبت خطاها در سرویسی مانند Sentry یا Bugsnag استفاده کنید. این به شما کمک میکند تا علت اصلی مشکلات را ردیابی کرده و پایداری برنامه خود را بهبود بخشید. - از مرزهای خطا برای خطاهای مورد انتظار استفاده نکنید: مرزهای خطا برای مدیریت خطاهای غیرمنتظره طراحی شدهاند. برای خطاهای مورد انتظار (مانند خطاهای اعتبارسنجی، خطاهای API)، از مکانیزمهای مدیریت خطای مشخصتری مانند بلوکهای
try...catch
یا کامپوننتهای مدیریت خطای سفارشی استفاده کنید. - سطوح چندگانه مرزهای خطا را در نظر بگیرید: میتوانید مرزهای خطا را تو در تو کنید تا سطوح مختلفی از مدیریت خطا را فراهم کنید. به عنوان مثال، ممکن است یک مرز خطای سراسری داشته باشید که هر خطای مدیریتنشدهای را میگیرد و یک پیام خطای عمومی نمایش میدهد، و مرزهای خطای مشخصتری که خطاها را در کامپوننتهای خاصی میگیرند و پیامهای خطای دقیقتری نمایش میدهند.
- رندر سمت سرور را فراموش نکنید: اگر از رندر سمت سرور استفاده میکنید، باید خطاها را در سمت سرور نیز مدیریت کنید. مرزهای خطا در سرور کار میکنند، اما ممکن است برای گرفتن خطاهایی که در طول رندر اولیه رخ میدهند، به مکانیزمهای مدیریت خطای اضافی نیاز داشته باشید.
تکنیکهای پیشرفته مرز خطا
۱. استفاده از Render Prop
به جای رندر کردن یک UI جایگزین ثابت، میتوانید از یک render prop برای ارائه انعطافپذیری بیشتر در نحوه مدیریت خطاها استفاده کنید. یک render prop یک prop تابعی است که یک کامپوننت برای رندر کردن چیزی از آن استفاده میکند.
class ErrorBoundary extends React.Component {
// ... (مانند قبل)
render() {
if (this.state.hasError) {
// از render prop برای رندر کردن UI جایگزین استفاده کنید
return this.props.fallbackRender(this.state.error, this.state.errorInfo);
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary fallbackRender={(error, errorInfo) => (
<div>
<h2>Something went wrong!</h2>
<p>Error: {error.message}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{errorInfo.componentStack}
</details>
</div>
)}>
<MyComponentThatMightThrow/>
</ErrorBoundary>
);
}
این به شما امکان میدهد UI جایگزین را به ازای هر مرز خطا سفارشی کنید. prop fallbackRender
خطا و اطلاعات خطا را به عنوان آرگومان دریافت میکند، که به شما امکان میدهد پیامهای خطای مشخصتری نمایش دهید یا اقدامات دیگری را بر اساس خطا انجام دهید.
۲. مرز خطا به عنوان یک کامپوننت مرتبه بالاتر (HOC)
شما میتوانید یک کامپوننت مرتبه بالاتر (HOC) ایجاد کنید که کامپوننت دیگری را با یک مرز خطا بپوشاند. این میتواند برای اعمال مرزهای خطا به چندین کامپوننت بدون نیاز به تکرار کد یکسان مفید باشد.
function withErrorBoundary(WrappedComponent) {
return class WithErrorBoundary extends React.Component {
render() {
return (
<ErrorBoundary>
<WrappedComponent {...this.props} />
</ErrorBoundary>
);
}
};
}
// نحوه استفاده:
const MyComponentWithErrorHandling = withErrorBoundary(MyComponentThatMightThrow);
تابع withErrorBoundary
یک کامپوننت را به عنوان آرگومان میگیرد و یک کامپوننت جدید برمیگرداند که کامپوننت اصلی را با یک مرز خطا میپوشاند. این به شما امکان میدهد به راحتی مدیریت خطا را به هر کامپوننتی در برنامه خود اضافه کنید.
تست کردن مرزهای خطا
تست کردن مرزهای خطای خود برای اطمینان از عملکرد صحیح آنها مهم است. میتوانید از کتابخانههای تست مانند Jest و React Testing Library برای تست مرزهای خطای خود استفاده کنید.
در اینجا یک مثال از نحوه تست یک مرز خطا با استفاده از React Testing Library آورده شده است:
import { render, screen, fireEvent } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
function ComponentThatThrows() {
throw new Error('This component throws an error');
}
test('renders fallback UI when an error is thrown', () => {
render(
<ErrorBoundary>
<ComponentThatThrows />
</ErrorBoundary>
);
expect(screen.getByText('Something went wrong.')).toBeInTheDocument();
});
این تست کامپوننت ComponentThatThrows
را رندر میکند که یک خطا پرتاب میکند. سپس تست تأیید میکند که UI جایگزین رندر شده توسط مرز خطا نمایش داده میشود.
مرزهای خطا و کامپوننتهای سرور (React 18+)
با معرفی کامپوننتهای سرور در React 18 و بالاتر، مرزهای خطا همچنان نقش حیاتی در مدیریت خطا ایفا میکنند. کامپوننتهای سرور در سرور اجرا میشوند و فقط خروجی رندر شده را به کلاینت ارسال میکنند. در حالی که اصول اصلی یکسان باقی میمانند، چند نکته ظریف برای در نظر گرفتن وجود دارد:
- ثبت خطای سمت سرور: اطمینان حاصل کنید که خطاهایی را که در کامپوننتهای سرور رخ میدهند، در سرور ثبت میکنید. این ممکن است شامل استفاده از یک فریمورک ثبت خطای سمت سرور یا ارسال خطاها به یک سرویس ردیابی خطا باشد.
- جایگزین سمت کلاینت: حتی اگر کامپوننتهای سرور در سرور رندر میشوند، شما همچنان باید یک UI جایگزین سمت کلاینت در صورت بروز خطا ارائه دهید. این تضمین میکند که کاربر تجربه یکسانی داشته باشد، حتی اگر سرور در رندر کردن کامپوننت شکست بخورد.
- Streaming SSR: هنگام استفاده از رندر سمت سرور جریانی (Streaming SSR)، خطاها میتوانند در طول فرآیند جریان رخ دهند. مرزهای خطا میتوانند به شما کمک کنند تا این خطاها را با رندر کردن یک UI جایگزین برای جریان آسیبدیده، به زیبایی مدیریت کنید.
مدیریت خطا در کامپوننتهای سرور یک حوزه در حال تکامل است، بنابراین مهم است که با آخرین بهترین شیوهها و توصیهها بهروز بمانید.
اشتباهات رایج که باید از آنها اجتناب کرد
- اتکای بیش از حد به مرزهای خطا: از مرزهای خطا به عنوان جایگزینی برای مدیریت خطای مناسب در کامپوننتهای خود استفاده نکنید. همیشه تلاش کنید کدی قوی و قابل اعتماد بنویسید که خطاها را به زیبایی مدیریت کند.
- نادیده گرفتن خطاها: مطمئن شوید خطاهایی را که توسط مرزهای خطا گرفته میشوند، ثبت میکنید تا بتوانید علت اصلی مشکلات را ردیابی کنید. صرفاً یک UI جایگزین نمایش ندهید و خطا را نادیده نگیرید.
- استفاده از مرزهای خطا برای خطاهای اعتبارسنجی: مرزهای خطا ابزار مناسبی برای مدیریت خطاهای اعتبارسنجی نیستند. به جای آن از تکنیکهای اعتبارسنجی مشخصتری استفاده کنید.
- تست نکردن مرزهای خطا: مرزهای خطای خود را تست کنید تا مطمئن شوید به درستی کار میکنند.
نتیجهگیری
مرزهای خطا ابزاری قدرتمند برای ساخت برنامههای React قوی و قابل اعتماد هستند. با درک نحوه پیادهسازی و استفاده مؤثر از مرزهای خطا، میتوانید تجربه کاربری را بهبود بخشید، از کرشهای برنامه جلوگیری کنید و دیباگ کردن را سادهتر سازید. به یاد داشته باشید که مرزهای خطا را به صورت استراتژیک قرار دهید، UI جایگزین آموزنده ارائه دهید، خطاها را ثبت کنید و مرزهای خطای خود را به طور کامل تست کنید.
با پیروی از دستورالعملها و بهترین شیوههای ذکر شده در این راهنما، میتوانید اطمینان حاصل کنید که برنامههای React شما در برابر خطاها مقاوم هستند و تجربه مثبتی را برای کاربران شما فراهم میکنند.