React: useEffect ودورة حياة التطبيق
آخر تحديث: 2026-10-26
إذا كان
useStateهو «ذاكرة» المكون، فإنuseEffectهو «الكاميرا» الخاصة به — فهو يتيح لك القيام ببعض الأمور الإضافية بعد عرض المكون على الشاشة («بعد التقاط الصورة»): طلب البيانات، والاشتراك في الأحداث، والتعديل على DOM، وضبط المؤقتات.
1. ما ستتعلمه
- الغرض من useEffect وطريقة استخدامه الأساسية
- ثلاثة أنماط لاستخدام المصفوفات
- وظائف التنظيف (لمنع تسربات الذاكرة)
- العلاقة بين
useEffectودورة حياة المكون - useEffect مقابل useLayoutEffect
2. قصة صفحة تحميل البيانات
(1) المشكلة: طلبات البيانات تتسبب في حلقة لا نهائية
تريد أليس طلب بيانات المستخدم عند تحميل المكون، لذا كتبت ما يلي:
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null)
// ❌ Error:Requesting data directly within a component's function body
fetch('/api/users/' + userId)
.then(res => res.json())
.then(data => setUser(data))
return <div>{user?.name}</div>
}
السؤال:
fetch→setUser(data)→ إعادة عرض المكون- إعادة العرض → تنفيذ
fetchمرة أخرى → إعادة تعيين الحالة → إعادة العرض... - حلقة لا نهائية! الصفحة متوقفة.
تحتاج أليس إلى طريقة لـ «التنفيذ مرة واحدة فقط عند تركيب المكون».
(2) حل useEffect
import { useState, useEffect } from 'react'
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
// ✅ Correct:useEffect + Empty array of dependencies
useEffect(() => {
fetch('/api/users/' + userId)
.then(res => {
if (!res.ok) throw new Error('Failed to load')
return res.json()
})
.then(data => {
setUser(data)
setLoading(false)
})
.catch(err => {
setError(err.message)
setLoading(false)
})
}, [userId]) // Only at userId Re-execute when changes occur
if (loading) return <p>Loading......</p>
if (error) return <p>Error:{error}</p>
return <h1>{user.name}</h1>
}
المزايا: useEffect يفصل الآثار الجانبية عن منطق العرض — حيث يقتصر دور العرض على واجهة المستخدم فقط، بينما يتم تنفيذ الآثار الجانبية بعد اكتمال عملية العرض.
3. أساسيات useEffect
(1) قواعد النحو الأساسية
useEffect(() => {
// Side Effect Logic(Execute after rendering is complete)
console.log('Component rendering complete!')
return () => {
// Cleanup Function(Run before unloading or re-executing a component)
console.log('Addressing Side Effects')
}
}, [Dependency Array])
| القسم | الوصف |
|---|---|
() => { ... } |
استدعاء رد الفعل الجانبي، الذي يتم تنفيذه بعد إتمام عرض المحتوى على الشاشة |
return () => { ... } |
وظيفة التنظيف؛ تُنفَّذ قبل إعادة التنفيذ نتيجة لإلغاء تثبيت أحد المكونات أو تغييرات في التبعيات |
[dependencies] |
مصفوفة تتحكم في توقيت إعادة تشغيل useEffect |
graph TB
A[Component Rendering] --> B[React Submit DOM Update]
B --> C[useEffect Callback Execution]
C --> D{Changes in Dependencies?}
D -->|is | E[Execute the previous cleanup function]
E --> F[Re-execute useEffect]
D -->|No| G[Skip]
F --> H{Component Uninstallation?}
G --> H
H -->|is | I[Execute the cleanup function]
style C fill:#61dafb,color:#000
style I fill:#ff6b6b,color:#fff
(2) ثلاثة أنماط لمصفوفات التبعية
// Pattern 1:Empty array [] — Execute only once when mounted
useEffect(() => {
console.log('Component Mounting')
fetch('/api/init').then(setData)
}, []) // Never re-execute
// Pattern 2:Dependencies [a, b] — Re-execute when dependencies change
useEffect(() => {
console.log('userId It has changed.:', userId)
fetch('/api/users/' + userId).then(setUser)
}, [userId]) // userId Re-execute when changes occur
// Pattern 3:No array dependencies — Executed on every render
useEffect(() => {
console.log('Executed on every render')
}) // Note:No array!
| مجموعة التبعيات | توقيت التنفيذ | حالات الاستخدام |
|---|---|---|
[] |
مرة واحدة فقط عند التثبيت | تهيئة طلبات البيانات والاشتراكات |
[dep1, dep2] |
عند التثبيت + عند تغير التبعيات | تغير البيانات مع المعلمات |
| مجموعات لا حصر لها | بعد كل عملية عرض | نادرًا ما تُستخدم (عادةً ما تُستبدل بطرق أخرى) |
4. وظائف التنظيف
تعد وظائف التنظيف أساسية لمنع تسربات الذاكرة وحالات التنافس.
(1) مسح المؤقت
▶ جرّب الكود(2) تنظيف مستمعي الأحداث
▶ جرّب الكود(3) طلب الإلغاء (لتجنب حالات التنافس)
function SearchResults({ query }) {
const [results, setResults] = useState([])
useEffect(() => {
let cancelled = false
fetch('/api/search?q=' + query)
.then(res => res.json())
.then(data => {
// ✅ If the component has been uninstalled or query Has changed,Do not update status
if (!cancelled) {
setResults(data)
}
})
// ✅ Cleanup:Uncheck
return () => {
cancelled = true
}
}, [query])
return <div>{results.map(r => <p key={r.id}>{r.name}</p>)}</div>
}
// When users quickly switch search terms:
// 1. Search"A" → useEffect Execute
// 2. Search"AB" → Clear Function Markers cancelled=true → useEffect Re-execute
// 3. Search"A" the response came back → cancelled=true → Do not update status(Avoid flashing)
الناتج:
العنوان الفرعي: "useEffect Life Cycle Demonstration". الحالة: count، logs. يتولى useEffect إدارة الآثار الجانبية. تحديث عبر مؤقت
▶ مثال: شرح شامل لوظائف التنظيف
▶ جرّب الكودالناتج:
العداد يزيد +1 تلقائيًا كل ثانيتين، ومفتاح المسافة +5. السجل: "⏱️ Timer started". التفكيك → "⏱️ Timer cleared"، "⌨️ Keyboard listening removed"
5. ربط useEffect بدورة حياة المكون
في عصر مكونات الفئات، كان هناك ثلاثة «هوكات» لدورة الحياة؛ أما في عصر «الهوكات»، useEffect—فإن «هوك» واحدًا يؤدي عمل ثلاثة:
graph LR
subgraph "The Lifecycle of Class Components"
A1[componentDidMount] --> A2[componentDidUpdate]
A2 --> A3[componentWillUnmount]
end
subgraph "Hooks 's useEffect"
B1["useEffect(fn, [])<br/>Execute when mounted"]
B2["useEffect(fn, [dep])<br/>Mount + run when deps change"]
B3["return cleanup<br/>Clean up before uninstalling or rerunning"]
end
الناتج:
العنوان الفرعي: "useEffect Life Cycle Demonstration". يعرض: "useEffect Life Cycle Demonstration". الحالة: count (setter: setCount)، logs (setter: setLogs). يتولى useEffect إدارة الآثار الجانبية. تحديث قائم على مؤقت
| دورة حياة مكونات الفئة | useEffect المقابل |
|---|---|
componentDidMount |
useEffect(() => { ... }, []) |
componentDidUpdate |
useEffect(() => { ... }) (مجموعات لا حصر لها) أو useEffect(() => { ... }, [dep]) |
وظائف التنظيف في componentWillUnmount |
useEffect(() => { return () => { ... } }, []) |
▶ مثال: استخدام useEffect واحد للتعامل مع مراحل متعددة من دورة الحياة
// ============================================
// Example:Chat Room Component——One useEffect Covering the Entire Lifecycle
// ============================================
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([])
useEffect(() => {
// === componentDidMount + componentDidUpdate ===
console.log(`🟢 Connect to the chat room:${roomId}`)
const connection = createConnection(roomId)
connection.connect()
connection.on('message', (msg) => {
setMessages(prev => [...prev, msg])
})
// === componentWillUnmount + Clean Up Before Re-Execution ===
return () => {
console.log(`🔴 Leave the chat room:${roomId}`)
connection.disconnect()
}
}, [roomId]) // roomId When things change:Disconnect the old connection → Join a New Chat Room
return (
<div>
<h3>Chat Room:{roomId}</h3>
{messages.map((msg, i) => <p key={i}>{msg}</p>}
</div>
)
}
// Usage
<ChatRoom roomId="general" /> // Connect general Chat Room
<ChatRoom roomId="random" /> // roomId Changes → Disconnect general → Connect random
// When a component is unloaded → Disconnect the current connection(Execution of the cleanup function)
الناتج:
عند التركيب: console "🟢 Connected to chat room: general". تبديل roomId: "🔴 Left: general" → "🟢 Connected: random". عند التفكيك: قطع الاتصال.
6. useEffect مقابل useLayoutEffect
يوفر React خطافين من نوع «الآثار الجانبية»، يختلفان من حيث توقيت التنفيذ.
graph LR
A[Trigger Rendering] --> B[Computational Virtual DOM]
B --> C[DOM Update]
C --> D{useLayoutEffect?}
D -->|is | E[Execute in parallel<br/>Blocking Browser Rendering]
D -->|No| F[Browser rendering complete]
F --> G[useEffect Asynchronous Execution]
style E fill:#faad14,color:#000
style G fill:#52c41a,color:#fff
| الخطاف | توقيت التنفيذ | يعيق عملية العرض | حالات الاستخدام |
|---|---|---|---|
useEffect |
بعد عرض المتصفح (بشكل غير متزامن) | ❌ | 90% من الحالات: طلبات البيانات، مستمعو الأحداث، التسجيل |
useLayoutEffect |
بعد تحديثات DOM، قبل عرض المتصفح (متزامن) | ✅ | قياس أبعاد DOM، وقراءة التخطيط، ومزامنة الرسوم المتحركة |
القواعد: ابدأ بكتابة
useEffect؛ ولا تنتقل إلىuseLayoutEffectإلا إذا واجهت مشكلات تتعلق بـ«الوميض» أو «تذبذب التخطيط».
7. مثال كامل: لوحة بيانات
▶ عرض المثال القابل للتشغيل (98 سطرًا)
الناتج:
🟢 متصل بغرفة الدردشة: ...
🔴 غادر غرفة الدردشة: ...
الناتج المتوقع: لوحة بيانات تحتوي على 4 بطاقات إحصائية، ومفتاح لتحديث البيانات في الوقت الفعلي، ومؤشر لحجم النافذة، ووقت آخر تحديث.
▶ المثال 3: استخدام useEffect لتنفيذ مزامنة البيانات في الوقت الفعلي
function LiveSearch({ apiBase }) {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
if (!query.trim()) { setResults([]); return }
setLoading(true)
const controller = new AbortController()
fetch(`${apiBase}/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
.then(res => res.json())
.then(data => { setResults(data); setLoading(false) })
.catch(err => { if (err.name !== 'AbortError') setLoading(false) })
return () => controller.abort()
}, [query, apiBase])
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<input value={query} onChange={e => setQuery(e.target.value)}
placeholder="Search users..." style={{ width: '100%', padding: 8, borderRadius: 4, marginBottom: 8 }} />
{loading && <p>Searching...</p>}
<ul style={{ listStyle: 'none', padding: 0 }}>
{results.map(r => <li key={r.id} style={{ padding: 4, borderBottom: '1px solid #f0f0f0' }}>{r.name}</li>)}
</ul>
</div>
)
}
الناتج:
يتولى useEffect إدارة الآثار الجانبية
الناتج:
يعرض: "Searching...". الحالة: query (setter: setQuery)، results (setter: setResults)، loading (setter: setLoading). الإدخال: Search users.... القائمة: {r.name}. يتولى useEffect إدارة الآثار الجانبية. جلب بيانات غير متزامن/حالة التحميل
▶ المثال 4: استخدام useEffect لمراقبة التغييرات في حجم النافذة
▶ جرّب الكودالناتج:
يتولى useEffect إدارة الآثار الجانبية
الناتج:
يعرض: "📱 Single column layout". الحالة: size (setter: setSize). يتولى useEffect إدارة الآثار الجانبية
▶ المثال 5: استخدام useEffect + localStorage للحفاظ على الحالة
▶ جرّب الكودالناتج:
تُحفظ قائمة المهام في localStorage. أضف مهمة، وانقر للتبديل (يتوسطها خط). تحديث الصفحة → تبقى المهام. الإضافة بمفتاح Enter.
❓ أسئلة شائعة
useEffect وكتابة الكود مباشرةً داخل المكون؟useEffect إلا بعد اكتمال عملية العرض، وبالتالي فهو لا يعطل واجهة المستخدم. أما الكود المكتوب مباشرةً داخل جسم دالة المكون فيتم تشغيله أثناء عملية العرض (مما يؤدي إلى تعطيلها) ويتم تنفيذه في كل عملية عرض. والفرق الرئيسي هو أن useEffect يسمح لك بإجراء عملية التنظيف عند إلغاء تثبيت المكون.useEffect قبل كل عملية إعادة تنفيذ؟userId، يتم تنظيف الطلب القديم قبل بدء طلب جديد، مما يضمن أن البيانات الجديدة تحل محل البيانات القديمة. إذا كان الطلب الجديد سيحل محل القديم مباشرةً، فقد يحدث خطأً يتمثل في «إكمال الطلب القديم أولاً → عودة البيانات إلى حالتها السابقة».useEffect؟useEffect التي تتولى «التهيئة» (مثل تحميل البيانات) أولاً، وتلك التي تتولى «الاستجابة للتغييرات» (مثل مزامنة الحالة مع localStorage) أخيرًا. أثناء عملية عرض واحدة، يتم تنفيذ جميع خطافات useEffect بالتسلسل.useEffect وuseLayoutEffect؟useEffect بشكل غير متزامن بعد تحديث DOM (بعد قيام المتصفح بعرض الصفحة)، وبالتالي لن يلاحظ المستخدمون أي وميض؛ أما useLayoutEffect فيُنفَّذ بشكل متزامن بعد تحديث DOM (قبل أن يقوم المتصفح بعرضه)، مما يتيح لك تعديل DOM ثم جعل المتصفح يعرضه. استخدم useEffect في 99% من الحالات. استخدم useLayoutEffect فقط عندما «تحتاج إلى إجراء حسابات بناءً على تخطيط DOM ثم تعديل DOM بشكل متزامن» (مثل تعديل موضع عنصر ما بعد قياس أبعاده).📖 ملخص
useEffect(fn, deps)تنفيذ الآثار الجانبية بعد اكتمال عملية العرض، دون تعطيل واجهة المستخدم- التبعية الفارغة
[]= تُنفَّذ مرة واحدة فقط أثناء عملية التثبيت؛ التبعية غير الفارغة = تُنفَّذ أثناء عملية التثبيت وعندما تتغير التبعيات - وظائف التنظيف
return () => {}هي عامل أساسي في منع تسربات الذاكرة — يجب تنظيف جميع المؤقتات ومستمعي الأحداث والاشتراكات - استخدم
useEffectفي 90% من الحالات؛ واستخدمuseLayoutEffectفقط عند قياس DOM. - يتم تنفيذ عدة خطافات
useEffectبالترتيب الذي كُتبت به
📝 تمارين
- المشكلة الأساسية (صعوبة ⭐): أنشئ مكونًا باسم
PageTitle. استخدمuseEffectلتغيير عنوان الصفحة إلى "مرحبًا بكم في موقعي الإلكتروني" عند تثبيت المكون، وإعادة العنوان إلى ما كان عليه عند إلغاء تثبيته. - تمرين متقدم (مستوى الصعوبة ⭐⭐): قم بإنشاء مكون
OnlineStatusيستخدمuseEffectللاستماع إلى الأحداث الواردة منnavigator.onLineوonline/offline، وعرض حالة الشبكة في الوقت الفعلي. - التحدي (الصعوبة: ⭐⭐⭐): أنشئ هوكًا مخصصًا باسم
usePollingيقبل المعلماتurlوinterval، ويقوم باستقصاء الواجهة مرة واحدة كل interval مللي ثانية، ويعيد القيمة{ data, isLoading, error }. يجب أن يدعم الهوك إيقاف الاستقصاء مؤقتًا واستئنافه.