الخطوات
-
1
إعداد الحالات الأساسية الثلاث
يحتاج كل مكوّن يجلب بيانات إلى ثلاث حالات: البيانات نفسها، وعلامة التحميل، والخطأ. ابدأ بهذه قبل كتابة أي سطر من كود fetch. تجنّب الرغبة في دمجها في كائن واحد مبكراً — ثلاث استدعاءات
useStateمنفصلة أوضح.jsximport { useState, useEffect } from 'react'; function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // منطق الجلب يأتي بعد ذلك } -
2
كتابة useEffect الأساسي للجلب
استدعِ
useEffectمع مصفوفة تبعيات فارغة ([]) للتشغيل مرة واحدة بعد التركيب. لا تجعل دالة callback للتأثيرasync— React لا تتعامل مع الـ Promise المُعادة. عرّف دالة async داخلية واستدعِها فوراً.jsxuseEffect(() => { async function fetchUser() { try { const res = await fetch(`/api/users/${userId}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); setUser(data); } catch (err) { setError(err.message); } finally { setLoading(false); } } fetchUser(); }, []); // تعمل مرة واحدة بعد التركيب -
3
رسم الحالات الثلاث
يجب أن يعالج JSX الخاص بك الحالات الثلاث صراحةً. مؤشر تحميل لا يختفي أبداً، أو شاشة بيضاء عند الخطأ، كلاهما أخطاء. عالجهما من البداية.
jsxif (loading) return <p>جاري التحميل...</p>; if (error) return <p>خطأ: {error}</p>; if (!user) return null; return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> ); -
4
إلغاء الطلب عند إزالة المكوّن
إذا أُزيل المكوّن قبل اكتمال الطلب — انتقل المستخدم بعيداً — سيكتمل الطلب ويستدعي
setUserعلى مكوّن غير مُركّب. هذا يسبب تحذير React وفي بعض الحالات أخطاء. استخدمAbortControllerلإلغاء الطلب في دالة التنظيف.jsxuseEffect(() => { const controller = new AbortController(); async function fetchUser() { try { const res = await fetch(`/api/users/${userId}`, { signal: controller.signal, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); setUser(data); } catch (err) { if (err.name === 'AbortError') return; // تجاهل — إلغاء متعمد setError(err.message); } finally { setLoading(false); } } fetchUser(); return () => controller.abort(); // تنظيف: إلغاء عند الإزالة }, [userId]); -
5
إعادة الجلب عند تغير التبعية
ضع أي متغير يعتمد عليه التأثير في مصفوفة التبعيات. هنا
userIdهو prop — عند تغيّره (تصفّح المستخدم لملف شخصي آخر)، يُعاد تشغيل التأثير لجلب البيانات الصحيحة. أعد ضبط حالة التحميل والخطأ في بداية كل تشغيل جلب.jsxuseEffect(() => { const controller = new AbortController(); setLoading(true); // إعادة الضبط عند كل userId جديد setError(null); async function fetchUser() { try { const res = await fetch(`/api/users/${userId}`, { signal: controller.signal, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); setUser(await res.json()); } catch (err) { if (err.name !== 'AbortError') setError(err.message); } finally { setLoading(false); } } fetchUser(); return () => controller.abort(); }, [userId]); // يُعاد التشغيل عند تغيّر userId -
6
فهم الاستدعاء المزدوج في Strict Mode
في بيئة التطوير، يقوم React 18 Strict Mode بتركيب كل مكوّن مرتين عمداً. سيُشغَّل
useEffectمرتين. هذا متعمد — يكشف التأثيرات التي تفتقر إلى دالة تنظيف. معAbortControllerفي مكانه بشكل صحيح، يُلغى الطلب الأول وينجح الثاني فقط. هذا هو السلوك المتوقع. لا تتحايل على ذلك بتعطيل Strict Mode. -
7
متى تستخدم React Query بدلاً من ذلك
بمجرد احتياج جلب البيانات إلى التخزين المؤقت، وإعادة الجلب في الخلفية، والصفحات، والتحديثات المتفائلة، أو إزالة التكرار، يصبح جلب
useEffectالمكتوب يدوياً عبئاً في الصيانة. React Query (أو SWR) يتعامل مع كل هذا وأكثر بواجهة API أصغر بكثير. المعيار تقريباً: إذا وجدت نفسك تكتب نفس نمط loading/error/refetch في أكثر من مكوّنين، الجأ إلى React Query.jsx// نفس الجلب مع React Query — لاحظ ما اختفى import { useQuery } from '@tanstack/react-query'; function UserProfile({ userId }) { const { data: user, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: () => fetch(`/api/users/${userId}`) .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); }), }); if (isLoading) return <p>جاري التحميل...</p>; if (error) return <p>خطأ: {error.message}</p>; return <h1>{user.name}</h1>; } // React Query يتعامل تلقائياً مع: التخزين المؤقت، إزالة التكرار، // إعادة الجلب في الخلفية، الإلغاء عند الإزالة، إعادة المحاولة عند الفشل
نصائح ومحاذير
- لا تجعل callback الخاصة بـ <code>useEffect</code> نفسها <code>async</code> أبداً. يجب أن تُعيد إما لا شيء أو دالة تنظيف — وليس Promise.
- قاعدة lint لمصفوفة التبعيات (<code>react-hooks/exhaustive-deps</code>) موجودة لسبب وجيه. إذا كان ESLint يطلب منك إضافة شيء للمصفوفة، استمع إليه — فهو محق دائماً تقريباً.
- إذا كنت بحاجة للجلب عند نقر زر (وليس عند التركيب)، لا تحتاج <code>useEffect</code> على الإطلاق. استدعِ دالة async مباشرة في معالج الحدث.
- تجنب تخزين البيانات المشتقة في state. إذا كان بإمكانك حساب شيء من <code>user</code> في دالة الرسم، لا تضعه في <code>useState</code> منفصل.
- للطلبات المتسلسلة (اجلب B فقط بعد اكتمال A)، سلسلها داخل <code>useEffect</code> واحد مع <code>AbortController</code> واحد — لا تتداخل استدعاءات <code>useEffect</code>.
خاتمة
النمط الكامل — ثلاث متغيرات state، ودالة async داخلية، وتنظيف AbortController، ومصفوفة تبعيات صحيحة — يغطي تقريباً كل حالة استخدام لجلب البيانات ستواجهها. الكود الاعتيادي يستحق الكتابة مرة واحدة لفهمه. بعد ذلك، React Query يُريحك من كتابته في كل مكوّن.