React: حدود الأخطاء وتصحيح الأخطاء
آخر تحديث: 2026-08-26
كان توم مسؤولاً عن البنية الخلفية للتجارة الإلكترونية، التي واجهت مشكلة «الشاشة البيضاء» على الموقع النشط — فقد أبلغ المستخدمون أن صفحة تفاصيل الطلب كانت فارغة تمامًا عند فتحها، وعرضت وحدة التحكم الخطأ
Cannot read properties of undefined (reading 'name'). كان السبب هو خطأ في JavaScript لم يتم اكتشافه، أطلقه مكون فرعي أثناء مرحلة العرض، مما تسبب في تعطل شجرة مكونات React بأكملها. يحتاج توم إلى إنشاء نظام حدود الأخطاء داخل التطبيق لضمان ألا يؤثر فشل المكونات الفردية على قابلية استخدام الصفحة بشكل عام.
1. ما ستتعلمه
- المبادئ وأفضل الممارسات المتعلقة بحدود الأخطاء
- كيفية تحليل مخططات اللهب في أداة تحليل الأداء (Profiler) في React DevTools
- يقيس مكون «Profiler» وقت عرض المكونات
- استراتيجيات تشخيص وحل مشكلات الأداء الشائعة
2. المخططات المفاهيمية
يوضح الشكل أدناه عملية التقاط «حدود الخطأ» وسلسلة التحليل في أداة «Profiler»:
flowchart TD
A[React Component tree] --> B[Error Boundary]
B --> C{Is there an error in the rendering of the child component??}
C -->|No| D[Normal Rendering]
C -->|is | E[getDerivedStateFromError]
E --> F[Update state.hasError = true]
F --> G[Rendering fallback UI]
G --> H{Click to retry??}
H -->|is | I[Reset state]
I --> A
H -->|No| J[Stay in the lower division UI]
K[React DevTools Profiler] --> L[Recording Component Rendering]
L --> M[Flame Pattern Analysis]
M --> N["Identify Time-Consuming Components(Yellow/Red)"]
N --> O[React.memo / useMemo Optimization]
style B fill:#e3f2fd,stroke:#1565c0
style E fill:#fff3e0,stroke:#e65100
style G fill:#e8f5e9,stroke:#2e7d32
style K fill:#f3e5f5,stroke:#7b1fa2
3. سيناريو واقعي
| مستوى معالجة الأخطاء | النطاق | استراتيجية الاستعادة | السيناريوهات القابلة للتطبيق |
|---|---|---|---|
| try/catch | عملية واحدة غير متزامنة | إعادة المحاولة/الخطة البديلة | طلبات واجهة برمجة التطبيقات (API)، عمليات Promise |
| حدود الخطأ | خطأ في عرض شجرة المكونات الفرعية | واجهة المستخدم الاحتياطية + زر إعادة المحاولة | حماية المكون من ظهور الشاشة البيضاء |
| حالات الرفض غير المعالجة على مستوى النظام | أخطاء الـ«Promise» غير المكتشفة | إعداد تقارير السجلات | المراقبة الشاملة |
| window.onerror | أخطاء التزامن العامة | إعداد تقارير السجلات | المراقبة الشاملة |
| React DevTools | تصحيح الأخطاء أثناء التطوير | حل المشكلات | حل مشكلات الأداء/العرض |
تتألف صفحة تفاصيل طلب توم من العناصر التالية:
OrderPage
├── OrderHeader (Order Number、Status)
├── OrderItems (Product List)
│ └── OrderItem × N(Single Item,Includes price calculation)
├── ShippingInfo (Shipping Information)
└── PaymentInfo (Payment Information)
كان سبب الحادث الذي وقع عبر الإنترنت هو عدم وجود الحقل price في بيانات المنتج الخاصة بطلب معين، وعندما قام المكون OrderItem بالوصول إلى item.price.toFixed(2)، أطلق خطأً TypeError، مما تسبب في ظهور شاشة فارغة في OrderPage بأكمله.
الطريقة الصحيحة هي إحاطة منطقة OrderItems بـ «حدود الخطأ» (Error Boundary) بحيث تظل رأس صفحة الطلب ومعلومات الدفع معروضة بشكل صحيح حتى في حالة فشل عرض قائمة المنتجات. بالإضافة إلى ذلك، يتعين على توم تعلم كيفية استخدام أداة تحليل الأداء (Profiler) في React DevTools لتحديد معوقات الأداء.
(1) حدود الخطأ — شبكة أمان على مستوى المكونات
«حدود الخطأ» (Error Boundary) هي آلية تعريفية لمعالجة الأخطاء توفرها React. عندما يطلق أي مكون في شجرة فرعية خطأً خلال مرحلة العرض، أو في إحدى طرق دورة الحياة، أو في منشئه، يمكن لـ «حدود الخطأ» التقاط هذا الخطأ وعرض واجهة مستخدم بديلة، بدلاً من التسبب في عرض شاشة فارغة في التطبيق بأكمله.
ملاحظة: لا يمكن حاليًا تنفيذ «Error Boundary» إلا باستخدام مكونات الفئات (تخطط React لتوفير نسخة باستخدام Hooks في الإصدارات المستقبلية).
▶ المثال 1: مكون ErrorBoundary عام
import { Component, ErrorInfo, ReactNode } from 'react'
interface ErrorBoundaryProps {
children: ReactNode
/** Custom Downgrade UI */
fallback?: ReactNode
/** Error Callback(Submit Sentry etc.) */
onError?: (error: Error, errorInfo: ErrorInfo) => void
}
interface ErrorBoundaryState {
hasError: boolean
error: Error | null
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props)
this.state = { hasError: false, error: null }
}
// Static Methods:Update Based on the Error state
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error }
}
// Life Cycle:Performing side effects after catching an error(Log Reporting)
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary An error was caught:', error.message)
console.error('Component Stack:', errorInfo.componentStack)
// Reported to the error monitoring service
if (this.props.onError) {
this.props.onError(error, errorInfo)
}
// Can be integrated into actual projects Sentry:
// Sentry.captureException(error, { extra: errorInfo })
}
handleReset = () => {
this.setState({ hasError: false, error: null })
}
render() {
if (this.state.hasError) {
// Use Custom fallback Or default to a lower level UI
if (this.props.fallback) {
return this.props.fallback
}
return (
<div
role="alert"
style={{
padding: '32px 24px',
margin: 16,
background: '#fff2f0',
border: '1px solid #ffccc7',
borderRadius: 8,
textAlign: 'center',
}}
>
<h2 style={{ color: '#ff4d4f', margin: '0 0 12px' }}>
A component error occurred
</h2>
<p style={{ color: '#666', marginBottom: 8, fontSize: 14 }}>
{this.state.error?.message || 'An unknown error has occurred'}
</p>
<button
onClick={this.handleReset}
style={{
padding: '6px 20px',
background: '#ff4d4f',
color: '#fff',
border: 'none',
borderRadius: 4,
cursor: 'pointer',
fontSize: 14,
}}
>
Retry
</button>
</div>
)
}
return this.props.children
}
}
export default ErrorBoundary
استخدام ErrorBoundary في المشاريع العملية
// Layered Wrapping — Each independent area has its own ErrorBoundary
function OrderPage({ orderId }: { orderId: string }) {
return (
<div>
{/* Order Header Information:It will display correctly even if there is an error below. */}
<ErrorBoundary fallback={<p>Failed to load order</p>}>
<OrderHeader orderId={orderId} />
</ErrorBoundary>
{/* Product List:Errors in one area do not affect other areas */}
<ErrorBoundary
onError={(err) => {
// Rendering Error in the List of Submitted Products
fetch('/api/log-error', {
method: 'POST',
body: JSON.stringify({ error: err.message, orderId }),
})
}}
>
<OrderItems orderId={orderId} />
</ErrorBoundary>
{/* Payment Information */}
<ErrorBoundary>
<PaymentInfo orderId={orderId} />
</ErrorBoundary>
</div>
)
}
▶ المثال 2: مكون UserProfile مع ميزة استعادة البيانات في حالة الخطأ
في السيناريوهات الواقعية، قد لا يكفي أحيانًا عرض واجهة المستخدم الاحتياطية وحدها — فقد يحتاج المستخدمون إلى تحديث بيانات معينة. وفيما يلي مثال على كيفية استخدام حدود الخطأ مع ميزة «إعادة المحاولة»:
import { useState } from 'react'
import ErrorBoundary from './ErrorBoundary'
// Simulating Data Retrieval That Results in Errors
function fetchUserData(userId: number) {
return fetch(`/api/users/${userId}`).then(res => {
if (!res.ok) throw new Error('Failed to retrieve user data')
return res.json()
})
}
// Data display components that may have rendering errors
function UserInfo({ userId }: { userId: number }) {
const [user, setUser] = useState<any>(null)
const [loading, setLoading] = useState(true)
useState(() => {
fetchUserData(userId)
.then(setUser)
.finally(() => setLoading(false))
})
if (loading) return <p>Loading......</p>
// If user Data Structure Exception,An error may occur here
return (
<div>
<h3>{user.name}</h3> {/* possibly:Cannot read properties of undefined */}
<p>{user.profile.bio}</p> {/* possibly:Cannot read properties of undefined */}
</div>
)
}
// Outer Container:With retries key Mechanism
function UserProfile({ userId }: { userId: number }) {
const [retryKey, setRetryKey] = useState(0)
return (
<ErrorBoundary
key={retryKey} // Change key It will unmount and remount the subtree
fallback={
<div style={{ padding: 24, textAlign: 'center' }}>
<p>Error loading user information</p>
<button onClick={() => setRetryKey(k => k + 1)}>
Retry Loading
</button>
</div>
}
>
<UserInfo userId={userId} />
</ErrorBoundary>
)
}
نصيحة مهمة: key={retryKey} اجعل ErrorBoundary يقوم بإلغاء ربط شجرته الفرعية وإعادة إنشائها عند تشغيل عملية إعادة المحاولة، وبذلك يتم إعادة تعيين حالة جميع المكونات التابعة.
(2) الأخطاء التي لا يمكن اكتشافها بواسطة حدود الأخطاء
Error Boundaries are not a panacea; they cannot catch the following four types of errors:
| نوع الخطأ | السبب | الحل |
|---|---|---|
| الأخطاء في معالجة الأحداث | عدم تنفيذ معالجات الأحداث أثناء مرحلة العرض | وضع منطق معالجة الأحداث داخل كتلة try/catch |
| الأخطاء في الكود غير المتزامن | لا تتم عمليات الاستدعاء المرتدة لـ setTimeout و Promise ضمن دورة عرض React | استخدم try/catch أو Promise.catch |
| الأخطاء في العرض من جانب الخادم (SSR) | حدود الأخطاء لا تسري إلا على جانب العميل | تغليف عملية العرض في كتل try/catch في حالة العرض من جانب الخادم |
| خطأ خاص بـ «Error Boundary» | يُطلق خطأً لا يمكن التقاطه بحد ذاته | قم بتغليفه بـ «Error Boundary» آخر في المستوى الخارجي |
معالجة الأحداث + المعالجة السليمة للأخطاء في الكود غير المتزامن
function PaymentForm() {
async function handleSubmit() {
try {
const result = await submitPayment()
// Processed successfully
} catch (error) {
// Asynchronous errors are caught here,Error Boundary That's none of my business
console.error('Payment Failed:', error)
// Display Error UI(For example, setting state)
setError(error instanceof Error ? error.message : 'Payment Failed')
}
}
// Errors in the event must also be used try/catch
function handleClick() {
try {
processPayment()
} catch (error) {
setError('Processing Failed,Please try again.')
}
}
}
(3) تحليل الأداء باستخدام أداة تحليل الأداء في React DevTools
تُعد علامة التبويب «Profiler» في React DevTools أداة أساسية لتحليل أداء عرض المكونات. فهي تُنشئ «رسمًا بيانيًّا على شكل لهب» يوضح بصريًّا وقت العرض لكل مكون.
تعليمات الاستخدام
1. Open your browser DevTools → Components Tabs
2. Switch to Profiler Sublabel
3. Click the blue record button(Start Recording)
4. Performing actions on the page(Click、Scrolling, etc.)
5. Click the Stop button(End Recording)
6. View the flame diagram
كيفية تفسير الرسوم البيانية الشعلية
┌────────────────────────────────────────────┐
│ App (0.3ms) │
│ ├── Navbar (0.2ms) │
│ ├── OrderPage (2.1ms) │
│ │ ├── OrderHeader (0.4ms) ── Gray │
│ │ ├── OrderItems (1.5ms) ── Yellow │
│ │ │ └── OrderItem × 20 (each 0.3ms) │
│ │ └── PaymentInfo (0.2ms) ── Gray │
│ └── Footer (0.1ms) │
└────────────────────────────────────────────┘
- رمادي: لا يتم إعادة العرض (السيناريو المثالي)
- أزرق: تم إعادة العرض، لكن وقت المعالجة كان طبيعيًا
- أصفر/أحمر: يستغرق العرض وقتًا طويلاً؛ يتطلب الانتباه
▶ المثال 3: قياس وقت العرض باستخدام مكون «Profiler»
يمكن لمكون <Profiler> المدمج في React قياس وقت عرض مكون معين في الكود الخاص بك بدقة، مما يجعله مناسبًا للمراقبة الآلية لمقاييس الأداء:
import { Profiler } from 'react'
type ProfilerPhase = 'mount' | 'update' | 'nested-update'
interface ProfileMetrics {
id: string
phase: ProfilerPhase
actualDuration: number // Actual rendering time for this render(milliseconds)
baseDuration: number // Worst-case runtime for a subtree
startTime: number // Render Start Timestamp
commitTime: number // Submit to DOM timestamp
interactions: Set<any> // Related Interaction Tracking
}
// Performance Monitoring Callbacks
function onRenderCallback(
id: string,
phase: ProfilerPhase,
actualDuration: number,
baseDuration: number,
startTime: number,
commitTime: number,
) {
// Record to the performance log
if (actualDuration > 16) { // More than 16ms = Frame drop threshold (60fps)
console.warn(
`[Performance Alerts] ${id} in ${phase} Time Taken per Stage ${actualDuration.toFixed(1)}ms,` +
`More than 16ms Frame Budget!`
)
// Reported to the performance monitoring system
// reportPerformance({ id, phase, actualDuration, baseDuration })
}
// Output from the development environment to the console
if (process.env.NODE_ENV === 'development') {
console.table({
'Components': id,
'Phase': phase,
'Actual time taken(ms)': actualDuration.toFixed(1),
'Benchmark Duration(ms)': baseDuration.toFixed(1),
})
}
}
// Big Data List——Potential Performance Bottlenecks
function ProductList({ products }: { products: Product[] }) {
return (
<Profiler id="ProductList" onRender={onRenderCallback}>
<div style={{ display: 'grid', gap: 16, gridTemplateColumns: 'repeat(3, 1fr)' }}>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
</Profiler>
)
}
استراتيجيات شائعة لتحسين الأداء
// 1. React.memo — Avoid Unnecessary Re-rendering
const ProductCard = React.memo(function ProductCard({
product,
}: {
product: Product
}) {
return (
<div style={{ border: '1px solid #eee', padding: 16, borderRadius: 8 }}>
<img src={product.image} alt={product.name} width="100%" />
<h4>{product.name}</h4>
<p>${product.price}</p>
</div>
)
})
// 2. useMemo — Cache the results of expensive computations
function OrderSummary({ items }: { items: OrderItem[] }) {
const totalPrice = useMemo(() => {
return items.reduce((sum, item) => {
// Assuming that complex currency conversions were performed here
return sum + convertCurrency(item.price, item.currency)
}, 0)
}, [items])
return <p>Total:${totalPrice.toFixed(2)}</p>
}
// 3. useCallback — Stable function references
function OrderList({ orders, onSelect }: {
orders: Order[]
onSelect: (id: string) => void
}) {
// ✅ use useCallback Keep references consistent
const handleSelect = useCallback((id: string) => {
onSelect(id)
}, [onSelect])
return orders.map(order => (
<OrderRow key={order.id} order={order} onSelect={handleSelect} />
))
}
(4) تصحيح الأخطاء باستخدام لوحة المكونات في React DevTools
بالإضافة إلى أداة «Profiler»، تُعد لوحة «Components» في React DevTools أيضًا أداة قوية لتصحيح الأخطاء في الاستخدام اليومي:
| الميزة | الغرض | طريقة التشغيل |
|---|---|---|
| تصفح شجرة المكونات | عرض التسلسل الهرمي للمكونات | انقر على DevTools → Components |
| عرض الخصائص/الحالة في الوقت الفعلي | التحقق من الحالة الحالية للمكون | اختيار مكون لعرض اللوحة اليمنى |
| تعديل الحالة مباشرةً | اختبار واجهة المستخدم في حالات مختلفة | النقر المزدوج على قيمة الحالة لتعديلها مباشرةً |
| البحث عن المكون | البحث السريع عن المكون | Ctrl+F أدخل اسم المكون |
| الانتقال إلى شفرة المصدر | عرض تنفيذ المكون | النقر على أيقونة <> |
// DevTools Components Panel Examples
<OrderPage>
<ErrorBoundary>
<OrderHeader
orderNumber="ORD-2026-0001" ← Props Real-time Display
status="shipped" ← Can be edited directly during testing
/>
</ErrorBoundary>
<ErrorBoundary>
<OrderItems>
<OrderItem product={...} /> ← State Expand to view
<OrderItem product={...} />
</OrderItems>
</ErrorBoundary>
</OrderPage>
▶ المثال 4: التعامل مع أخطاء طلبات واجهة برمجة التطبيقات (API) — استرداد البيانات باستخدام آليات إعادة المحاولة
function useFetchWithRetry(url, maxRetries = 3) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [loading, setLoading] = useState(true)
const [retries, setRetries] = useState(0)
const fetchData = useCallback(async () => {
setLoading(true)
setError(null)
try {
const res = await fetch(url)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const json = await res.json()
setData(json)
} catch (err) {
if (retries < maxRetries) {
setRetries(r => r + 1)
setTimeout(fetchData, 1000 * (retries + 1))
} else {
setError(err.message)
}
} finally {
setLoading(false)
}
}, [url, retries, maxRetries])
useEffect(() => { fetchData() }, [url])
return { data, error, loading, retries, refetch: () => { setRetries(0); fetchData() } }
}
function UserList() {
const { data: users, error, loading, retries, refetch } = useFetchWithRetry('/api/users')
if (loading) return <p>Loading... {retries > 0 && `(retry ${retries})`}</p>
if (error) return (
<div style={{ padding: 20, textAlign: 'center' }}>
<p style={{ color: '#ff4d4f' }}>Error: {error}</p>
<button onClick={refetch} style={{ padding: '8px 16px', cursor: 'pointer' }}>Retry</button>
</div>
)
return (
<ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
)
}
▶ المثال 5: مراقبة الأخطاء على المستوى العام — التكامل مع Sentry
// lib/errorReporting.ts
const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN
function initErrorReporting() {
if (typeof window === 'undefined') return
if (!SENTRY_DSN) return
// Sentry.init({ dsn: SENTRY_DSN, ... })
// Simplified Example:Simulation Using Global Event Listeners
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled Promise:', event.reason)
reportError({
type: 'unhandledrejection',
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
timestamp: new Date().toISOString(),
})
})
window.addEventListener('error', (event) => {
console.error('Global Error:', event.error)
reportError({
type: 'window.error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
timestamp: new Date().toISOString(),
})
})
}
function reportError(payload) {
fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}).catch(() => {})
}
// app/layout.tsx
function RootLayout({ children }) {
useEffect(() => { initErrorReporting() }, [])
return <html><body>{children}</body></html>
}
❓ أسئلة شائعة
try/catch)، والأخطاء في الكود غير المتزامن (استخدم Promise.catch)، والأخطاء أثناء العرض من جانب الخادم، والأخطاء داخل حدود الخطأ نفسها. عند تصميم معالجة الأخطاء، تحتاج إلى الجمع بين try/catch وحدود الخطأ في نهج متعدد الطبقات.React.memo بتحسين جميع المكونات تلقائيًا؟useMemo / useCallback للحفاظ على استقرار المراجع، أو تمرير حجة ثانية — وهي دالة مقارنة مخصصة React.memo(Comp, (prev, next) => deepEqual(prev, next)) — إلى React.memo.<Profiler> على الأداء في بيئة الإنتاج؟<Profiler> في زيادة طفيفة في عبء الأداء في بيئة الإنتاج. يُنصح باستخدامه فقط في بيئات التطوير أو التحكم فيه عبر متغيرات البيئة: {process.env.NODE_ENV === 'development' && <Profiler>...}. عندما تكون مراقبة الأداء مطلوبة في بيئة الإنتاج، فكر في استخدام مكتبات مخصصة لمراقبة الأداء (مثل web-vitals) أو ميزات تتبع الأداء في Sentry.getDerivedStateFromError وcomponentDidCatch، واللتان لا تدعمهما سوى مكونات الفئات. وقد أشار فريق React إلى احتمال توفر نسخة Hook في المستقبل، ولكن في الوقت الحالي (React 18/19)، لا يمكن تنفيذها إلا باستخدام مكونات الفئة. يمكنك إنشاء مكون «حدود الخطأ» قائم على الفئة ثم تغليفه في مكون دالة للتعامل مع منطق استعادة الخطأ.📖 ملخص
- «Error Boundary» هو الحل التصريحي الذي توفره React لمعالجة الأخطاء، والذي يمنع حدوث تعطل واحد من أن يتسبب في ظهور شاشة فارغة في التطبيق بأكمله.
- لا يمكن تنفيذ «حدود الخطأ» إلا باستخدام مكونات الفئة، من خلال التعاون بين طريقتي دورة الحياة
getDerivedStateFromErrorوcomponentDidCatch. - يتيح لك تغيير
keyفي «حدود الخطأ» إعادة تعيين (إعادة تركيب) الشجرة الفرعية. - لا يمكن لـ «Error Boundary» اكتشاف الأخطاء في معالجات الأحداث، أو في الكود غير المتزامن، أو في عملية التوليد الجانبي للصفحة (SSR)، أو داخلها نفسها
- يعرض «React DevTools Profiler» أوقات العرض باستخدام «رسم بياني على شكل لهب»؛ والمكونات ذات اللونين الأصفر والأحمر هي تلك التي تحتاج إلى تحسين.
- تُعد React.memo وuseMemo وuseCallback «الثلاثي المقدس» لتحسين أداء React
📝 تمارين
- قم بإنشاء مكون
ErrorBoundaryواستخدمه بشكل هرمي ضمن مكوناتOrderPage: WrapOrderHeader,OrderItems, andPaymentInfoin separateErrorBoundary. قم بتحفيز خطأ في العرض يدويًّا (مثل تمرير خصائص غير صحيحة) للتحقق من أن المنطقة المتأثرة فقط هي التي تعرض واجهة المستخدم الاحتياطية، بينما يتم عرض بقية الواجهة بشكل طبيعي. - استخدم المكون
Profilerلقياس وقت عرض مكون يحتوي على 100 عنصر قائمة. بعد إجراء التحسين باستخدامReact.memo، قم بقياس وقت العرض مرة أخرى وقارن الفرق فيactualDurationبين القياسين للتحقق من فعالية عملية التحسين. - افتح أداة تحليل الأداء (React DevTools Profiler) في متصفح Chrome، وسجل تفاعلًا مع الصفحة (مثل البحث أو التصفية أو الفرز)، وحدد المكون الذي يستغرق أطول وقت في العرض في مخطط اللهب (flame graph)، وحلل السبب، وقم بتحسينه باستخدام
useMemo/useCallback.