React
useEffect في React — دليل شامل للتأثيرات الجانبية 2026
📅 2026-10-15⏱ 8 دقائق قراءة
في المقال السابق، تعلمت الأحداث والنماذج. الآن سنتعلم **useEffect** — وهو Hook يسمح بتشغيل **تأثيرات جانبية** (Side Effects) داخل المكونات.
في هذا الدليل العملي، سنأخذك خطوة بخطوة لفهم useEffect ومتى تستخدمه، مع تمارين وحلول.
## ما هو Side Effect؟
**Side Effect** (تأثير جانبي) هو أي شيء **يحدث خارج المكوّنة**:
- طلب API (Fetch).
- تعديل DOM مباشرة.
- تعيين مؤقت (setTimeout).
- الاشتراك في event.
- تعديل `document.title`.
**⚠️ مشكلة:** لا يمكن وضع هذه العمليات **داخل** render (سيسبب مشاكل).
**✅ الحل:** استخدم `useEffect`.
## لماذا نحتاج useEffect؟
**دورة حياة المكوّنة:**
```
1. Mount (التركيب) → useEffect بدون dependencies
2. Update (التحديث) → useEffect مع dependencies
3. Unmount (الإزالة) → useEffect مع cleanup
```
## الصيغة الأساسية
```tsx
import { useEffect } from "react";
useEffect(() => {
// الكود الذي ينفذ
return () => {
// دالة التنظيف (اختياري)
};
}, [dependencies]);
```
## حالات useEffect
### 1. بدون dependencies — ينفذ بعد كل render
```tsx
useEffect(() => {
console.log("ينفذ بعد كل render");
});
```
**⚠️ استخدم بحذر** — قد يسبب حلقات لا نهائية.
### 2. مع مصفوفة فارغة — ينفذ مرة واحدة (Mount)
```tsx
useEffect(() => {
console.log("ينفذ مرة واحدة فقط");
}, []);
```
**الاستخدام:** جلب بيانات أولية، إعداد مكتبة، ...
### 3. مع dependencies — ينفذ عند تغير القيم
```tsx
useEffect(() => {
console.log("ينفذ عندما يتغير count");
}, [count]);
```
**الاستخدام:** الاستجابة لتغير قيمة معينة.
## دالة التنظيف (Cleanup)
```tsx
useEffect(() => {
const timer = setInterval(() => {
console.log("tick");
}, 1000);
// دالة التنظيف:
return () => {
clearInterval(timer);
};
}, []);
```
**⚠️ مهم:** دالة التنظيف:
- تُنفذ عند **إزالة المكوّنة**.
- أو قبل **إعادة تنفيذ useEffect**.
## أمثلة عملية
### 1. تعديل عنوان الصفحة
```tsx
function PageTitle() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `العدد: ${count}`;
}, [count]);
return <button onClick={() => setCount(count + 1)}>+1</button>;
}
```
**كل نقرة تُحدّث عنوان الصفحة!**
### 2. المؤقت (Timer)
```tsx
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((s) => s + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <p>مرّ {seconds} ثانية</p>;
}
```
### 3. جلب بيانات من API
```tsx
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => {
setUsers(data);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <p>جاري التحميل...</p>;
if (error) return <p>خطأ: {error}</p>;
return (
<ul>
{users.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
```
### 4. الاعتماد على متغير
```tsx
function SearchUsers() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
if (query === "") {
setResults([]);
return;
}
const timer = setTimeout(() => {
fetch(`https://api.example.com/search?q=${query}`)
.then((res) => res.json())
.then(setResults);
}, 500);
return () => clearTimeout(timer);
}, [query]);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>{results.map((r: any) => <li key={r.id}>{r.name}</li>)}</ul>
</div>
);
}
```
**الفكرة:** Debouncing — لا يرسل الطلب إلا بعد 500 مللي ثانية من التوقف عن الكتابة.
### 5. الاشتراك في event
```tsx
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <p>عرض الشاشة: {width}px</p>;
}
```
## useEffect مع TypeScript
### 1. جلب بيانات
```tsx
interface User {
id: number;
name: string;
email: string;
}
function Users() {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
async function fetchUsers() {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data: User[] = await res.json();
setUsers(data);
}
fetchUsers();
}, []);
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
```
### 2. دالة التنظيف
```tsx
useEffect(() => {
const timer: NodeJS.Timeout = setTimeout(() => {
console.log("بعد ثانية");
}, 1000);
return () => clearTimeout(timer);
}, []);
```
## useEffect vs useLayoutEffect
<table>
<thead>
<tr>
<th>المعيار</th>
<th>useEffect</th>
<th>useLayoutEffect</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>التوقيت</strong></td>
<td>بعد الرسم</td>
<td>قبل الرسم</td>
</tr>
<tr>
<td><strong>الأداء</strong></td>
<td>✅ أفضل</td>
<td>⚠️ يحجب الرسم</td>
</tr>
<tr>
<td><strong>الاستخدام</strong></td>
<td>معظم الحالات</td>
<td>قياس DOM</td>
</tr>
</tbody>
</table>
**القاعدة:** استخدم `useEffect` دائماً، إلا إذا كنت تقيس DOM.
## أخطاء شائعة
### 🔴 الخطأ 1: حلقة لا نهائية
```tsx
// ❌ خطأ:
useEffect(() => {
setCount(count + 1); // تحديث state ← إعادة render ← useEffect ← ...
}, [count]);
```
**الحل:** لا تحدّث state الذي تعتمد عليه.
### 🔴 الخطأ 2: نسيان dependencies
```tsx
// ❌ خطأ: لا يتحدث عند تغير count
useEffect(() => {
document.title = `العدد: ${count}`;
}, []); // ← count مفقود
// ✅ صحيح:
useEffect(() => {
document.title = `العدد: ${count}`;
}, [count]);
```
### 🔴 الخطأ 3: نسيان دالة التنظيف
```tsx
// ❌ خطأ: تسريب ذاكرة
useEffect(() => {
const timer = setInterval(() => {}, 1000);
// لا يوجد cleanup
}, []);
// ✅ صحيح:
useEffect(() => {
const timer = setInterval(() => {}, 1000);
return () => clearInterval(timer);
}, []);
```
### 🔴 الخطأ 4: استخدام async مباشرة
```tsx
// ❌ خطأ:
useEffect(async () => {
const data = await fetch("/api");
}, []);
// ✅ صحيح:
useEffect(() => {
async function fetchData() {
const data = await fetch("/api");
}
fetchData();
}, []);
```
## تمارين عملية
### تمرين 1: عنوان ديناميكي
أنشئ مكوّنة تحدّث عنوان الصفحة بعدّاد.
**الحل:**
```tsx
function TitleUpdater() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `العدد: ${count}`;
}, [count]);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
```
### تمرين 2: مؤقت تنازلي
أنشئ مؤقتاً يبدأ من 10 وينتهي عند 0.
**الحل:**
```tsx
function Countdown() {
const [count, setCount] = useState(10);
useEffect(() => {
if (count === 0) return;
const timer = setTimeout(() => setCount(count - 1), 1000);
return () => clearTimeout(timer);
}, [count]);
return <p>{count === 0 ? "⏰ انتهى!" : count}</p>;
}
```
### تمرين 3: جلب مستخدمين
اجلب قائمة مستخدمين من API واعرضها.
**الحل:**
```tsx
function UsersList() {
const [users, setUsers] = useState<any[]>([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then(setUsers);
}, []);
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
```
### تمرين 4: عرض حجم الشاشة
اعرض عرض الشاشة وحدّثه عند تغيير الحجم.
**الحل:**
```tsx
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handler = () => setWidth(window.innerWidth);
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, []);
return <p>العرض: {width}px</p>;
}
```
### تمرين 5: العدّاد التلقائي
أنشئ عدّاداً يزيد تلقائياً كل ثانية.
**الحل:**
```tsx
function AutoCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => setCount((c) => c + 1), 1000);
return () => clearInterval(interval);
}, []);
return <p>{count}</p>;
}
```
### تمرين 6: البحث مع Debounce
أنشئ حقل بحث لا يرسل الطلب إلا بعد 500ms.
**الحل:**
```tsx
function DebouncedSearch() {
const [query, setQuery] = useState("");
const [results, setResults] = useState<string[]>([]);
useEffect(() => {
if (!query) {
setResults([]);
return;
}
const timer = setTimeout(() => {
setResults([`نتيجة 1 لـ "${query}"`, `نتيجة 2 لـ "${query}"`]);
}, 500);
return () => clearTimeout(timer);
}, [query]);
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>{results.map((r, i) => <li key={i}>{r}</li>)}</ul>
</div>
);
}
```
### تمرين 7: تسجيل الدخول التلقائي
أنشئ مكوّنة تسجّل رسالة عند mount و unmount.
**الحل:**
```tsx
function Logger() {
useEffect(() => {
console.log("🟢 تم التركيب");
return () => {
console.log("🔴 تم الإزالة");
};
}, []);
return <p>افتح Console</p>;
}
```
### تمرين 8: مراقبة الشبكة
أضف مستمعاً لحدث `online` و `offline`.
**الحل:**
```tsx
function NetworkStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const online = () => setIsOnline(true);
const offline = () => setIsOnline(false);
window.addEventListener("online", online);
window.addEventListener("offline", offline);
return () => {
window.removeEventListener("online", online);
window.removeEventListener("offline", offline);
};
}, []);
return <p>{isOnline ? "🟢 متصل" : "🔴 غير متصل"}</p>;
}
```
## حل المشاكل الشائعة
### 🔴 المشكلة 1: حلقة لا نهائية
**السبب:** تحديث state داخل useEffect مع dependency عليه.
**الحل:** احذف dependency أو لا تحدّث state.
### 🔴 المشكلة 2: البيانات لا تُحدّث
**السبب:** dependency فارغ `[]`.
**الحل:** أضف المتغيرات التي يعتمد عليها الكود.
### 🔴 المشكلة 3: تسريب ذاكرة
**السبب:** عدم تنظيف `setInterval`/`setTimeout`.
**الحل:** استخدم دالة التنظيف.
### 🔴 المشكلة 4: `Warning: Can't perform a React state update on an unmounted component`
**السبب:** تحديث state بعد إزالة المكوّنة.
**الحل:** استخدم flag:
```tsx
useEffect(() => {
let isMounted = true;
fetch("/api").then((data) => {
if (isMounted) setData(data);
});
return () => {
isMounted = false;
};
}, []);
```
## جدول useEffect
<table>
<thead>
<tr>
<th>الصيغة</th>
<th>متى ينفذ؟</th>
<th>الاستخدام</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>useEffect(() => {})</code></td>
<td>بعد كل render</td>
<td>⚠️ نادراً</td>
</tr>
<tr>
<td><code>useEffect(() => {}, [])</code></td>
<td>مرة واحدة (Mount)</td>
<td>✅ جلب بيانات</td>
</tr>
<tr>
<td><code>useEffect(() => {}, [dep])</code></td>
<td>عند تغير dep</td>
<td>✅ الاستجابة</td>
</tr>
<tr>
<td><code>return () => {}</code></td>
<td>عند Unmount</td>
<td>✅ التنظيف</td>
</tr>
</tbody>
</table>
## قائمة تحقق نهائية
<table>
<thead>
<tr>
<th>المهمة</th>
<th>الحالة</th>
</tr>
</thead>
<tbody>
<tr>
<td>فهم Side Effects</td>
<td>⬜</td>
</tr>
<tr>
<td>useEffect مع dependency فارغ</td>
<td>⬜</td>
</tr>
<tr>
<td>useEffect مع dependencies</td>
<td>⬜</td>
</tr>
<tr>
<td>دالة التنظيف (Cleanup)</td>
<td>⬜</td>
</tr>
<tr>
<td>جلب بيانات من API</td>
<td>⬜</td>
</tr>
<tr>
<td>Debouncing</td>
<td>⬜</td>
</tr>
<tr>
<td>حل التمارين الثمانية</td>
<td>⬜</td>
</tr>
</tbody>
</table>
## ماذا بعد هذا المقال؟
الآن بعد أن أتقنت useEffect، أنت جاهز للمقال التالي:
1. **القوائم والشروط** — العرض الديناميكي.
2. **React Router** — التنقل بين الصفحات.
3. **Context API** — إدارة الحالة العامة.
## الخلاصة
في هذا المقال، تعلمت:
- ✅ ما هو Side Effect ولماذا نحتاجه.
- ✅ الصيغة الأساسية لـ useEffect.
- ✅ الحالات الثلاث (Mount, Update, Unmount).
- ✅ دالة التنظيف (Cleanup).
- ✅ جلب بيانات من API.
- ✅ Debouncing.
- ✅ حل 8 تمارين عملية.
**تذكر:** useEffect قوي لكن **لا تبالغ في استخدامه**. أحياناً هناك حلول أفضل.
**هل أتقنت useEffect؟ شاركنا في التعليقات!**