حدود الأخطاء و Suspense
مقدمة إلى معالجة الأخطاء في React
يوفر React آليات قوية للتعامل مع الأخطاء بأناقة: حدود الأخطاء تلتقط أخطاء JavaScript في أشجار المكونات، بينما يدير Suspense العمليات غير المتزامنة وحالات التحميل. معًا، يخلقان تجارب مستخدم مرنة.
حدود الأخطاء: التقاط أخطاء المكونات
حدود الأخطاء هي مكونات React تلتقط أخطاء JavaScript في أي مكان في شجرة المكونات الفرعية، وتسجل الأخطاء، وتعرض واجهة مستخدم احتياطية. تعمل مثل كتل try/catch في JavaScript ولكن للمكونات.
مهم: يجب أن تكون حدود الأخطاء مكونات فئة. لا يمكن أن تكون المكونات الوظيفية حدود أخطاء (بعد)، لكن يمكن تغليفها بها.
// src/components/ErrorBoundary.jsx
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
// يُستدعى عند طرح خطأ أثناء العرض
static getDerivedStateFromError(error) {
// تحديث الحالة بحيث يعرض العرض التالي واجهة المستخدم الاحتياطية
return { hasError: true };
}
// يُستدعى بعد طرح خطأ
componentDidCatch(error, errorInfo) {
// تسجيل الخطأ في خدمة الإبلاغ عن الأخطاء
console.error('خطأ تم التقاطه بواسطة الحد:', error, errorInfo);
// يمكنك التسجيل في خدمات مثل Sentry، LogRocket، إلخ.
// logErrorToService(error, errorInfo);
this.setState({
error,
errorInfo
});
}
resetError = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
};
render() {
if (this.state.hasError) {
// واجهة المستخدم الاحتياطية
return (
<div style={{
padding: '2rem',
backgroundColor: '#fee',
border: '1px solid #fcc',
borderRadius: '0.5rem'
}}>
<h2>حدث خطأ ما</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
<summary>تفاصيل الخطأ</summary>
<p>{this.state.error && this.state.error.toString()}</p>
<p>{this.state.errorInfo && this.state.errorInfo.componentStack}</p>
</details>
<button onClick={this.resetError}>حاول مرة أخرى</button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
استخدام حدود الأخطاء
قم بتغليف المكونات التي قد تطرح أخطاء بحد الأخطاء:
import ErrorBoundary from './components/ErrorBoundary';
import UserProfile from './components/UserProfile';
import Dashboard from './components/Dashboard';
function App() {
return (
<div>
<h1>تطبيقي</h1>
{/* تغليف المكونات الفردية */}
<ErrorBoundary>
<UserProfile userId={123} />
</ErrorBoundary>
{/* أو تغليف أقسام كاملة */}
<ErrorBoundary>
<Dashboard>
<Stats />
<Charts />
<RecentActivity />
</Dashboard>
</ErrorBoundary>
</div>
);
}
// مكون قد يطرح خطأ
function BuggyComponent({ user }) {
if (!user) {
throw new Error('المستخدم مطلوب!');
}
return <div>{user.name}</div>;
}
حدود الأخطاء لا تلتقط:
- الأخطاء في معالجات الأحداث (استخدم try/catch)
- الكود غير المتزامن (setTimeout، الوعود)
- أخطاء العرض من جانب الخادم
- الأخطاء المطروحة في حد الأخطاء نفسه
حد أخطاء متقدم مع التسجيل
أنشئ حد أخطاء جاهز للإنتاج مع تتبع الأخطاء:
import React from 'react';
class ProductionErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
eventId: null
};
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// التسجيل في خدمة خارجية في الإنتاج
if (process.env.NODE_ENV === 'production') {
// مثال: تكامل Sentry
// const eventId = Sentry.captureException(error, {
// contexts: { react: { componentStack: errorInfo.componentStack } }
// });
// this.setState({ eventId });
// أو خدمة التسجيل المخصصة الخاصة بك
fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: error.toString(),
componentStack: errorInfo.componentStack,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href
})
});
}
this.setState({ error, errorInfo });
}
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>عفوًا! حدث خطأ ما</h2>
<p>تم إخطارنا ونحن نعمل على حل المشكلة.</p>
{process.env.NODE_ENV === 'development' && (
<details>
<summary>تفاصيل الخطأ (التطوير فقط)</summary>
<pre>{this.state.error.toString()}</pre>
<pre>{this.state.errorInfo.componentStack}</pre>
</details>
)}
<button onClick={() => window.location.reload()}>
إعادة تحميل الصفحة
</button>
</div>
);
}
return this.props.children;
}
}
Suspense: إدارة العمليات غير المتزامنة
يتيح Suspense للمكونات "الانتظار" لشيء ما قبل العرض، مما يظهر واجهة مستخدم احتياطية أثناء التحميل:
import { Suspense, lazy } from 'react';
// تحميل المكونات كسولًا
const HeavyComponent = lazy(() => import('./components/HeavyComponent'));
const Dashboard = lazy(() => import('./components/Dashboard'));
const UserProfile = lazy(() => import('./components/UserProfile'));
function App() {
return (
<div>
{/* Suspense أساسي مع احتياطي تحميل */}
<Suspense fallback={<div>جاري التحميل...</div>}>
<HeavyComponent />
</Suspense>
{/* مكونات متعددة في Suspense واحد */}
<Suspense fallback={<LoadingSpinner />}>
<Dashboard />
<UserProfile userId={123} />
</Suspense>
{/* حدود Suspense المتداخلة */}
<Suspense fallback={<div>جاري تحميل الصفحة...</div>}>
<MainLayout>
<Suspense fallback={<div>جاري تحميل الشريط الجانبي...</div>}>
<Sidebar />
</Suspense>
<Suspense fallback={<div>جاري تحميل المحتوى...</div>}>
<Content />
</Suspense>
</MainLayout>
</Suspense>
</div>
);
}
// مكون تحميل مخصص
function LoadingSpinner() {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '200px'
}}>
<div className="spinner">جاري التحميل...</div>
</div>
);
}
دمج حدود الأخطاء و Suspense
استخدم كليهما معًا لمعالجة أخطاء قوية وحالات تحميل:
import { Suspense, lazy } from 'react';
import ErrorBoundary from './components/ErrorBoundary';
const AsyncComponent = lazy(() => import('./components/AsyncComponent'));
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<LoadingFallback />}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
);
}
// أفضل: فصل حالات التحميل والأخطاء
function RobustAsyncLoader({ children }) {
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<Suspense fallback={<LoadingFallback />}>
{children}
</Suspense>
</ErrorBoundary>
);
}
// الاستخدام
function Dashboard() {
return (
<div>
<h1>لوحة التحكم</h1>
<RobustAsyncLoader>
<AsyncCharts />
</RobustAsyncLoader>
<RobustAsyncLoader>
<AsyncUserList />
</RobustAsyncLoader>
</div>
);
}
واجهة مستخدم تحميل هيكلية
أنشئ تجارب تحميل أفضل بشاشات هيكلية:
// مكون هيكلي لحالة التحميل
function UserCardSkeleton() {
return (
<div className="user-card skeleton">
<div className="skeleton-avatar"></div>
<div className="skeleton-text"></div>
<div className="skeleton-text short"></div>
</div>
);
}
/* CSS الهيكلي */
.skeleton {
animation: pulse 1.5s ease-in-out infinite;
}
.skeleton-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
background: #e0e0e0;
}
.skeleton-text {
height: 16px;
background: #e0e0e0;
border-radius: 4px;
margin: 8px 0;
}
.skeleton-text.short {
width: 60%;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
// الاستخدام مع Suspense
function UserList() {
return (
<Suspense fallback={
<div>
<UserCardSkeleton />
<UserCardSkeleton />
<UserCardSkeleton />
</div>
}>
<AsyncUserList />
</Suspense>
);
}
استراتيجيات استعادة الأخطاء
نفّذ استراتيجيات استعادة مختلفة بناءً على أنواع الأخطاء:
import React from 'react';
class SmartErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
retryCount: 0
};
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('خطأ:', error, errorInfo);
}
handleRetry = () => {
this.setState(prevState => ({
hasError: false,
error: null,
retryCount: prevState.retryCount + 1
}));
};
handleReload = () => {
window.location.reload();
};
handleGoHome = () => {
window.location.href = '/';
};
render() {
if (this.state.hasError) {
const { error, retryCount } = this.state;
const isNetworkError = error.message.includes('network') ||
error.message.includes('fetch');
return (
<div className="error-recovery">
<h2>حدث خطأ ما</h2>
{isNetworkError && (
<>
<p>تم اكتشاف مشكلة في اتصال الشبكة.</p>
<button onClick={this.handleRetry}>
إعادة المحاولة ({3 - retryCount} محاولات متبقية)
</button>
</>
)}
{!isNetworkError && (
<>
<p>حدث خطأ غير متوقع.</p>
<button onClick={this.handleReload}>إعادة تحميل الصفحة</button>
<button onClick={this.handleGoHome}>العودة إلى الرئيسية</button>
</>
)}
{retryCount >= 3 && (
<p>
إذا استمرت المشكلة، يرجى الاتصال بالدعم.
</p>
)}
</div>
);
}
return this.props.children;
}
}
أفضل ممارسة: ضع حدود الأخطاء بشكل استراتيجي على مستويات مختلفة—واحد في جذر التطبيق للأخطاء الكارثية، وآخرين حول الميزات للتعامل المعزول مع الأخطاء.
الميزات المتزامنة مع Suspense
React 18+ Suspense يعمل مع الميزات المتزامنة لتجربة مستخدم أفضل:
import { Suspense, useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const handleSearch = (e) => {
const value = e.target.value;
// وضع علامة على تحديث الحالة كغير عاجل
startTransition(() => {
setQuery(value);
});
};
return (
<div>
<input
type="text"
placeholder="بحث..."
onChange={handleSearch}
style={{ opacity: isPending ? 0.5 : 1 }}
/>
<Suspense fallback={<div>جاري البحث...</div>}>
<ResultsList query={query} />
</Suspense>
</div>
);
}
تمرين 1: نظام حدود الأخطاء
المهمة: بناء نظام شامل لمعالجة الأخطاء:
- أنشئ مكون حد أخطاء بواجهة مستخدم احتياطية مخصصة
- أضف تسجيل الأخطاء إلى وحدة التحكم (محاكاة خدمة خارجية)
- نفّذ وظيفة إعادة المحاولة
- أنشئ مكونًا معطوبًا يطرح أخطاء عند النقر على زر
- أظهر رسائل خطأ مختلفة لأنواع أخطاء مختلفة
تمرين 2: لوحة تحكم بالتحميل الكسول
المهمة: بناء لوحة تحكم بأقسام محملة كسولًا:
- أنشئ مكونات منفصلة للرسوم البيانية والإحصائيات وأقسام النشاط
- حمّل كل قسم كسولًا بـ
React.lazy() - أضف حدود Suspense بواجهة مستخدم تحميل هيكلية
- اغلف كل قسم في حدود الأخطاء
- أضف زر "إعادة تحميل القسم" في احتياطي الخطأ
تمرين 3: محمل بيانات غير متزامن قوي
المهمة: أنشئ مكون محمل بيانات غير متزامن قابل لإعادة الاستخدام:
- ادمج حد الأخطاء و Suspense
- اقبل خصائص العرض لحالات التحميل والخطأ والنجاح
- نفّذ إعادة محاولة تلقائية بتراجع أسي
- أضف معالجة المهلة
- اعرض عدد إعادة المحاولة والوقت المقدر لإعادة المحاولة التالية
الخلاصة
في هذا الدرس، أتقنت معالجة الأخطاء وإدارة العمليات غير المتزامنة في React:
- إنشاء مكونات حد الأخطاء للتقاط الأخطاء والتعامل معها بأناقة
- فهم ما يمكن وما لا يمكن لحدود الأخطاء التقاطه
- تطبيق تسجيل وتتبع الأخطاء الجاهز للإنتاج
- استخدام Suspense للتحميل الكسول والعمليات غير المتزامنة
- دمج حدود الأخطاء و Suspense للتطبيقات القوية
- بناء واجهات مستخدم تحميل هيكلية لتجربة مستخدم أفضل
- تنفيذ استراتيجيات استعادة الأخطاء الذكية
- الاستفادة من الميزات المتزامنة للأداء الأمثل
مع إتقان أساسيات React.js هذه، أنت جاهز لبناء تطبيقات بجودة الإنتاج بمعالجة أخطاء مناسبة وأداء محسّن وتجربة مستخدم ممتازة!