React: useEffect ودورة حياة التطبيق
آخر تحديث: 2026-08-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) مسح المؤقت
function Timer() {
const [seconds, setSeconds] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setSeconds(s => s + 1)
}, 1000)
// ✅ Clear the timer when the component is unloaded
return () => {
clearInterval(timer)
console.log('The timer has been cleared.')
}
}, []) // Start only when mounted
return <p>Running: {seconds}s</p>
}
// If you don't clean it,:The timer continues to run even after the component is unloaded,Error "Update on unmounted components"
(2) تنظيف مستمعي الأحداث
function WindowSize() {
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })
useEffect(() => {
function handleResize() {
setSize({ width: window.innerWidth, height: window.innerHeight })
}
window.addEventListener('resize', handleResize)
// ✅ Remove event listeners when the component is unmounted
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return <p>Window:{size.width} × {size.height}</p>
}
(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)
▶ مثال: شرح شامل لوظائف التنظيف
// ============================================
// Example:useEffect Declaration Cycle Demonstration
// Features:Start the timer when mounted + Listening for Keyboard Events
// Clean up all resources upon uninstallation
// ============================================
function LifecycleDemo() {
const [count, setCount] = useState(0)
const [logs, setLogs] = useState([])
// Effect 1:Title Update
useEffect(() => {
document.title = `Count:${count}`
}, [count])
// Effect 2:Timer
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1)
}, 2000)
addLog('⏱️ The timer has started')
return () => {
clearInterval(id)
addLog('⏱️ The timer has been cleared.')
}
}, [])
// Effect 3:Keyboard Events
useEffect(() => {
function handleKey(e) {
if (e.key === ' ') {
setCount(c => c + 5)
}
}
window.addEventListener('keydown', handleKey)
return () => {
window.removeEventListener('keydown', handleKey)
addLog('⌨️ Keyboard listening has been removed')
}
}, [])
function addLog(msg) {
setLogs(prev => [...prev, `${new Date().toLocaleTimeString()} ${msg}`])
}
return (
<div>
<h2>useEffect Life Cycle Demonstration</h2>
<p>Count:{count} (+1 every 2s, Space +5)</p>
<div style={{ maxHeight: '200px', overflow: 'auto', backgroundColor: '#f5f5f5', padding: '10px', borderRadius: '4px', fontSize: '12px', fontFamily: 'monospace' }}>
{logs.map((log, i) => <div key={i}>{log}</div>)}
</div>
</div>
)
}
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 المقابل |
|---|---|
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)
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، وقراءة التخطيط، ومزامنة الرسوم المتحركة |
import { useState, useEffect, useLayoutEffect } from 'react'
function MeasureExample() {
const [width, setWidth] = useState(0)
const ref = useRef(null)
// ❌ useEffect Read DOM Dimensions → Users may notice flickering
useEffect(() => {
setWidth(ref.current.offsetWidth) // Read After Drawing → May trigger additional rendering
}, [])
// ✅ useLayoutEffect → Read Before Drawing,Transparent to the user
useLayoutEffect(() => {
setWidth(ref.current.offsetWidth)
}, [])
return <div ref={ref}>Width:{width}px</div>
}
القواعد: ابدأ بكتابة
useEffect؛ ولا تنتقل إلىuseLayoutEffectإلا إذا واجهت مشكلات تتعلق بـ«الوميض» أو «تذبذب التخطيط».
7. مثال كامل: لوحة بيانات
// ============================================
// Complete Example:Data Dashboard(useEffect Integrated Applications)
// Features:Refresh data at regular intervals + Window Adaptation + Resource Cleanup
// ============================================
import { useState, useEffect } from 'react'
function Dashboard() {
const [data, setData] = useState(null)
const [isLive, setIsLive] = useState(true)
const [lastUpdate, setLastUpdate] = useState(null)
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
})
// Effect 1:First Load + Refresh at set intervals
useEffect(() => {
function fetchData() {
// Simulation API Request
const mockData = {
users: Math.floor(Math.random() * 1000) + 500,
orders: Math.floor(Math.random() * 200) + 100,
revenue: Math.floor(Math.random() * 50000) + 10000,
growth: (Math.random() * 20 - 5).toFixed(1)
}
setData(mockData)
setLastUpdate(new Date().toLocaleTimeString())
}
// Execute once immediately
fetchData()
// Refresh at set intervals
let interval
if (isLive) {
interval = setInterval(fetchData, 5000)
}
return () => {
clearInterval(interval)
}
}, [isLive]) // isLive Reset the timer when a change occurs
// Effect 2:Window Size Monitoring
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
})
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
// Effect 3:Page Title
useEffect(() => {
document.title = `Dashboard - ${data ? `${data.users} User` : 'Loading......'}`
}, [data])
// Effect 4:Leave Page Prompt
useEffect(() => {
function handleBeforeUnload(e) {
if (isLive) {
e.preventDefault()
e.returnValue = ''
}
}
window.addEventListener('beforeunload', handleBeforeUnload)
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
}, [isLive])
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
<h2>📊 Real-Time Dashboard</h2>
<div>
<span style={{ color: '#666', fontSize: '14px', marginRight: '12px' }}>
Window:{windowSize.width}×{windowSize.height}
</span>
<label>
<input type="checkbox" checked={isLive} onChange={e => setIsLive(e.target.checked)} />
Refreshes in real time
</label>
</div>
</div>
{!data ? (
<p style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
⏳ Loading......
</p>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}>
<StatCard label="Total Users" value={data.users.toLocaleString()} color="#1890ff" />
<StatCard label="Today's Orders" value={data.orders.toLocaleString()} color="#52c41a" />
<StatCard label="Monthly Income" value={`$${data.revenue.toLocaleString()}`} color="#ff4d4f" />
<StatCard label="Growth Rate" value={`${data.growth}%`} color={parseFloat(data.growth) >= 0 ? '#52c41a' : '#ff4d4f'} />
</div>
)}
<p style={{ textAlign: 'right', color: '#999', fontSize: '12px', marginTop: '12px' }}>
Last Updated:{lastUpdate} {isLive ? '(Auto-refresh every 5s)' : '(Suspended)'}
</p>
</div>
)
}
function StatCard({ label, value, color }) {
return (
<div style={{
border: '1px solid #f0f0f0',
borderRadius: '8px',
padding: '20px',
textAlign: 'center',
boxShadow: '0 2px 4px rgba(0,0,0,0.05)'
}}>
<p style={{ color: '#666', margin: '0 0 8px 0' }}>{label}</p>
<p style={{ fontSize: '28px', fontWeight: 'bold', color, margin: 0 }}>{value}</p>
</div>
)
}
الناتج المتوقع: لوحة بيانات تحتوي على 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>
)
}
▶ المثال 4: استخدام useEffect لمراقبة التغييرات في حجم النافذة
function useWindowSize() {
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })
useEffect(() => {
function handleResize() {
setSize({ width: window.innerWidth, height: window.innerHeight })
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return size
}
function ResponsiveLayout() {
const { width } = useWindowSize()
const layout = width < 640 ? 'mobile' : width < 1024 ? 'tablet' : 'desktop'
return (
<div style={{ padding: 16, maxWidth: 600, margin: '0 auto' }}>
<h3>Viewport: {width}px ({layout})</h3>
{layout === 'mobile' && <p>📱 Single column layout</p>}
{layout === 'tablet' && <p>📟 Two column layout</p>}
{layout === 'desktop' && <p>💻 Three column layout with sidebar</p>}
</div>
)
}
▶ المثال 5: استخدام useEffect + localStorage للحفاظ على الحالة
function usePersistedState(key, initial) {
const [value, setValue] = useState(() => {
try {
const saved = localStorage.getItem(key)
return saved !== null ? JSON.parse(saved) : initial
} catch { return initial }
})
useEffect(() => {
try { localStorage.setItem(key, JSON.stringify(value)) }
catch (e) { console.error('Save failed:', e) }
}, [key, value])
return [value, setValue]
}
function TodoApp() {
const [todos, setTodos] = usePersistedState('todos', [])
const [input, setInput] = useState('')
function addTodo() {
if (!input.trim()) return
setTodos(prev => [...prev, { id: Date.now(), text: input.trim(), done: false }])
setInput('')
}
function toggleTodo(id) {
setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t))
}
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Todos (persisted)</h3>
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={input} onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && addTodo()}
placeholder="Add task..." style={{ flex: 1, padding: 8, borderRadius: 4 }} />
<button onClick={addTodo} style={{ padding: '8px 16px', cursor: 'pointer' }}>Add</button>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map(t => (
<li key={t.id} onClick={() => toggleTodo(t.id)}
style={{ padding: 4, cursor: 'pointer', textDecoration: t.done ? 'line-through' : 'none', color: t.done ? '#999' : '#333' }}>
{t.text}
</li>
))}
</ul>
</div>
)
}
❓ أسئلة شائعة
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 }. يجب أن يدعم الهوك إيقاف الاستقصاء مؤقتًا واستئنافه.