دليل شامل لفهم وتطبيق حدود أخطاء جافاسكريبت (Error Boundaries) في React لمعالجة قوية للأخطاء وتدهور سلس لواجهة المستخدم.
حدود أخطاء جافاسكريبت: دليل تطبيقي لمعالجة الأخطاء في React
في عالم تطوير React، يمكن أن تؤدي الأخطاء غير المتوقعة إلى تجارب مستخدم محبطة وعدم استقرار التطبيق. تعد استراتيجية معالجة الأخطاء المحددة جيدًا أمرًا بالغ الأهمية لبناء تطبيقات قوية وموثوقة. توفر حدود الأخطاء (Error Boundaries) في React آلية قوية لمعالجة الأخطاء التي تحدث داخل شجرة المكونات الخاصة بك بأمان، مما يمنع التطبيق بأكمله من الانهيار ويسمح لك بعرض واجهة مستخدم بديلة.
ما هي حدود الأخطاء (Error Boundary)؟
حدود الأخطاء (Error Boundary) هي مكونات React تلتقط أخطاء جافاسكريبت في أي مكان داخل شجرة المكونات الفرعية الخاصة بها، وتسجل تلك الأخطاء، وتعرض واجهة مستخدم بديلة بدلاً من شجرة المكونات التي انهارت. تلتقط حدود الأخطاء الأخطاء أثناء العرض (rendering)، وفي دوال دورة الحياة (lifecycle methods)، وفي مُنشِئات (constructors) الشجرة بأكملها أسفلها.
فكر في حدود الأخطاء ككتلة try...catch
لمكونات React. تمامًا كما تسمح لك كتلة try...catch
بمعالجة الاستثناءات في كود جافاسكريبت المتزامن، تسمح لك حدود الأخطاء بمعالجة الأخطاء التي تحدث أثناء عرض مكونات React الخاصة بك.
ملاحظة هامة: حدود الأخطاء لا تلتقط الأخطاء في:
- معالجات الأحداث (event handlers) (اعرف المزيد في الأقسام التالية)
- الكود غير المتزامن (e.g.,
setTimeout
orrequestAnimationFrame
callbacks) - العرض من جانب الخادم (Server-side rendering)
- الأخطاء التي تحدث في حدود الأخطاء نفسها (بدلاً من مكوناتها الفرعية)
لماذا نستخدم حدود الأخطاء؟
يقدم استخدام حدود الأخطاء العديد من المزايا الهامة:
- تحسين تجربة المستخدم: بدلاً من عرض شاشة بيضاء فارغة أو رسالة خطأ غامضة، يمكنك عرض واجهة مستخدم بديلة سهلة الاستخدام، لإعلام المستخدم بحدوث خطأ ما وربما تقديم طريقة للاسترداد (على سبيل المثال، إعادة تحميل الصفحة أو الانتقال إلى قسم مختلف).
- استقرار التطبيق: تمنع حدود الأخطاء الأخطاء في جزء واحد من تطبيقك من التسبب في انهيار التطبيق بأكمله. وهذا مهم بشكل خاص للتطبيقات المعقدة التي تحتوي على العديد من المكونات المترابطة.
- معالجة مركزية للأخطاء: توفر حدود الأخطاء موقعًا مركزيًا لتسجيل الأخطاء وتتبع السبب الجذري للمشكلات. هذا يبسط تصحيح الأخطاء والصيانة.
- التدهور السلس (Graceful Degradation): يمكنك وضع حدود الأخطاء بشكل استراتيجي حول أجزاء مختلفة من تطبيقك لضمان أنه حتى لو فشلت بعض المكونات، يظل باقي التطبيق يعمل. وهذا يسمح بالتدهور السلس في مواجهة الأخطاء.
تطبيق حدود الأخطاء في React
لإنشاء حدود أخطاء، تحتاج إلى تعريف مكون فئة (class component) يطبق إحدى (أو كلتا) دالتي دورة الحياة التاليتين:
static getDerivedStateFromError(error)
: يتم استدعاء دالة دورة الحياة هذه بعد أن يلقي مكون فرعي خطأً. تتلقى الخطأ الذي تم إلقاؤه كوسيط ويجب أن تعيد قيمة لتحديث حالة المكون للإشارة إلى حدوث خطأ (على سبيل المثال، تعيين علامة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) {
// تحديث الحالة حتى يُظهر العرض التالي واجهة المستخدم البديلة.
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) {
// يمكنك عرض أي واجهة مستخدم بديلة مخصصة
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>
أمثلة عملية لاستخدام حدود الأخطاء
دعنا نستكشف بعض السيناريوهات العملية التي يمكن أن تكون فيها حدود الأخطاء مفيدة بشكل خاص:
1. التعامل مع أخطاء 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 أو أعادت خطأ، فإن حدود الأخطاء ستلتقط الخطأ وتعرض واجهة مستخدم بديلة (محددة داخل دالة render
الخاصة بحدود الأخطاء). هذا يمنع التطبيق بأكمله من الانهيار ويوفر للمستخدم رسالة أكثر إفادة. يمكنك توسيع واجهة المستخدم البديلة لتوفير خيار لإعادة محاولة الطلب.
2. التعامل مع أخطاء المكتبات الخارجية
عند استخدام مكتبات خارجية، من الممكن أن تطلق أخطاء غير متوقعة. يمكن أن يساعد تغليف المكونات التي تستخدم هذه المكتبات بحدود الأخطاء في التعامل مع هذه الأخطاء بأمان.
فكر في مكتبة رسوم بيانية افتراضية تطلق أحيانًا أخطاء بسبب عدم اتساق البيانات أو مشكلات أخرى. يمكنك تغليف مكون الرسم البياني على النحو التالي:
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
خطأ، فستلتقطه حدود الأخطاء وتعرض واجهة مستخدم بديلة. لاحظ أن try/catch داخل MyChartComponent سيلتقط فقط الأخطاء داخل الدالة المتزامنة، وليس دورة حياة المكون. لذلك، فإن حدود الأخطاء حاسمة هنا.
3. التعامل مع أخطاء العرض
يمكن أن تحدث الأخطاء أثناء عملية العرض بسبب بيانات غير صالحة أو أنواع خصائص (props) غير صحيحة أو مشكلات أخرى. يمكن لحدود الأخطاء التقاط هذه الأخطاء ومنع التطبيق من الانهيار.
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} /> <!-- نوع خاصية غير صحيح -->
</ErrorBoundary>
);
}
في هذا المثال، يتوقع مكون DisplayName
أن تكون الخاصية name
سلسلة نصية. إذا تم تمرير رقم بدلاً من ذلك، فسيتم إلقاء خطأ، وستلتقطه حدود الأخطاء وتعرض واجهة مستخدم بديلة.
حدود الأخطاء ومعالجات الأحداث
كما ذكرنا سابقًا، حدود الأخطاء لا تلتقط الأخطاء التي تحدث داخل معالجات الأحداث. هذا لأن معالجات الأحداث عادة ما تكون غير متزامنة، وحدود الأخطاء تلتقط فقط الأخطاء التي تحدث أثناء العرض، وفي دوال دورة الحياة، وفي المنشئات.
لمعالجة الأخطاء في معالجات الأحداث، تحتاج إلى استخدام كتلة 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
يمنع المتصفح من عرض رسالة الخطأ الافتراضية. ومع ذلك، كن على دراية بتجربة المستخدم؛ إذا قمت بكبت الرسالة الافتراضية، فتأكد من توفير بديل واضح ومفيد.
أفضل الممارسات لاستخدام حدود الأخطاء
فيما يلي بعض أفضل الممارسات التي يجب مراعاتها عند استخدام حدود الأخطاء:
- ضع حدود الأخطاء بشكل استراتيجي: قم بتغليف أجزاء مختلفة من تطبيقك بحدود الأخطاء لعزل الأخطاء ومنعها من الانتشار. فكر في تغليف مسارات كاملة أو أقسام رئيسية من واجهة المستخدم الخاصة بك.
- وفر واجهة مستخدم بديلة مفيدة: يجب أن تخبر الواجهة البديلة المستخدم بحدوث خطأ وربما تعرض طريقة للاسترداد. تجنب عرض رسائل خطأ عامة مثل "حدث خطأ ما".
- سجل الأخطاء: استخدم دالة دورة الحياة
componentDidCatch
لتسجيل الأخطاء في خدمة مثل Sentry أو Bugsnag. سيساعدك هذا في تتبع السبب الجذري للمشكلات وتحسين استقرار تطبيقك. - لا تستخدم حدود الأخطاء للأخطاء المتوقعة: تم تصميم حدود الأخطاء للتعامل مع الأخطاء غير المتوقعة. بالنسبة للأخطاء المتوقعة (مثل أخطاء التحقق من الصحة، أخطاء API)، استخدم آليات معالجة أخطاء أكثر تحديدًا، مثل كتل
try...catch
أو مكونات معالجة الأخطاء المخصصة. - فكر في مستويات متعددة من حدود الأخطاء: يمكنك تداخل حدود الأخطاء لتوفير مستويات مختلفة من معالجة الأخطاء. على سبيل المثال، قد يكون لديك حدود أخطاء عالمية تلتقط أي أخطاء غير معالجة وتعرض رسالة خطأ عامة، وحدود أخطاء أكثر تحديدًا تلتقط الأخطاء في مكونات معينة وتعرض رسائل خطأ أكثر تفصيلاً.
- لا تنس العرض من جانب الخادم: إذا كنت تستخدم العرض من جانب الخادم، فستحتاج إلى معالجة الأخطاء على الخادم أيضًا. تعمل حدود الأخطاء على الخادم، ولكن قد تحتاج إلى استخدام آليات إضافية لمعالجة الأخطاء التي تحدث أثناء العرض الأولي.
تقنيات متقدمة لحدود الأخطاء
1. استخدام خاصية العرض (Render Prop)
بدلاً من عرض واجهة مستخدم بديلة ثابتة، يمكنك استخدام خاصية العرض (render prop) لتوفير مرونة أكبر في كيفية معالجة الأخطاء. خاصية العرض هي خاصية دالة يستخدمها المكون لعرض شيء ما.
class ErrorBoundary extends React.Component {
// ... (نفس ما سبق)
render() {
if (this.state.hasError) {
// استخدام خاصية العرض (render prop) لعرض واجهة المستخدم البديلة
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>
);
}
يسمح لك هذا بتخصيص واجهة المستخدم البديلة على أساس كل حدود أخطاء على حدة. تتلقى خاصية fallbackRender
الخطأ ومعلومات الخطأ كوسائط، مما يسمح لك بعرض رسائل خطأ أكثر تحديدًا أو اتخاذ إجراءات أخرى بناءً على الخطأ.
2. حدود الأخطاء كمكون عالي الرتبة (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
، الذي يلقي خطأ. ثم يؤكد الاختبار أنه يتم عرض واجهة المستخدم البديلة التي تعرضها حدود الأخطاء.
حدود الأخطاء ومكونات الخادم (React 18+)
مع إدخال مكونات الخادم في React 18 والإصدارات الأحدث، تواصل حدود الأخطاء لعب دور حيوي في معالجة الأخطاء. يتم تنفيذ مكونات الخادم على الخادم وترسل فقط المخرجات المعروضة إلى العميل. بينما تظل المبادئ الأساسية كما هي، هناك بعض الفروق الدقيقة التي يجب مراعاتها:
- تسجيل الأخطاء من جانب الخادم: تأكد من تسجيل الأخطاء التي تحدث داخل مكونات الخادم على الخادم. قد يتضمن ذلك استخدام إطار عمل لتسجيل الأخطاء من جانب الخادم أو إرسال الأخطاء إلى خدمة تتبع الأخطاء.
- واجهة مستخدم بديلة من جانب العميل: على الرغم من أن مكونات الخادم يتم عرضها على الخادم، إلا أنك لا تزال بحاجة إلى توفير واجهة مستخدم بديلة من جانب العميل في حالة حدوث أخطاء. يضمن هذا أن يكون لدى المستخدم تجربة متسقة، حتى لو فشل الخادم في عرض المكون.
- البث في SSR: عند استخدام العرض من جانب الخادم (SSR) بالبث، يمكن أن تحدث أخطاء أثناء عملية البث. يمكن أن تساعدك حدود الأخطاء في التعامل مع هذه الأخطاء بأمان عن طريق عرض واجهة مستخدم بديلة للتيار المتأثر.
تعد معالجة الأخطاء في مكونات الخادم مجالًا متطورًا، لذا من المهم البقاء على اطلاع بأحدث أفضل الممارسات والتوصيات.
المزالق الشائعة التي يجب تجنبها
- الاعتماد المفرط على حدود الأخطاء: لا تستخدم حدود الأخطاء كبديل للمعالجة السليمة للأخطاء في مكوناتك. اسعَ دائمًا لكتابة كود قوي وموثوق يتعامل مع الأخطاء بأمان.
- تجاهل الأخطاء: تأكد من تسجيل الأخطاء التي تلتقطها حدود الأخطاء حتى تتمكن من تتبع السبب الجذري للمشكلات. لا تكتفِ بعرض واجهة مستخدم بديلة وتجاهل الخطأ.
- استخدام حدود الأخطاء لأخطاء التحقق من الصحة: حدود الأخطاء ليست الأداة المناسبة للتعامل مع أخطاء التحقق من الصحة. استخدم تقنيات تحقق أكثر تحديدًا بدلاً من ذلك.
- عدم اختبار حدود الأخطاء: اختبر حدود الأخطاء الخاصة بك للتأكد من أنها تعمل بشكل صحيح.
الخلاصة
تعد حدود الأخطاء أداة قوية لبناء تطبيقات React قوية وموثوقة. من خلال فهم كيفية تطبيق واستخدام حدود الأخطاء بفعالية، يمكنك تحسين تجربة المستخدم، ومنع انهيار التطبيقات، وتبسيط عملية تصحيح الأخطاء. تذكر وضع حدود الأخطاء بشكل استراتيجي، وتوفير واجهة مستخدم بديلة مفيدة، وتسجيل الأخطاء، واختبار حدود الأخطاء الخاصة بك بدقة.
باتباع الإرشادات وأفضل الممارسات الموضحة في هذا الدليل، يمكنك التأكد من أن تطبيقات React الخاصة بك مرنة في مواجهة الأخطاء وتوفر تجربة إيجابية للمستخدمين.