النمط الأساسي: useState + useEffect + fetch
ثلاث حالات نحتاجها دائمًا: البيانات، التحميل، الخطأ.
import { useState, useEffect } from "react";
import {
HttpClient, HttpUserRepository, UserRepository, UserDTO,
AsyncState, useAsyncService, ApiError, NetworkError,
} from "./lib/react-base-repository";
// 1. Repository — الطبقة الوحيدة التي تعرف fetch
// لا يُسمح باستدعاء fetch/http مباشرة من المكوّنات
const http = new HttpClient("https://api.example.com");
const userRepo: UserRepository = new HttpUserRepository(http);
// 2. Service Hook — يغلّف Repository ويعرض حالة المكوّن
function useUsers(): AsyncState<UserDTO[]> & { refetch: () => void } {
const result = useAsyncService(() => userRepo.getUsers());
useEffect(() => { result.refetch(); }, [result.refetch]);
return result;
}
// 3. المكوّن — لا يعرف fetch ولا axios ولا Repository
// يتعامل فقط مع hooks و domain exceptions
function Users() {
const { status, data: users, error, refetch } = useUsers();
if (status === 'loading') return <p>جارٍ التحميل...</p>;
if (status === 'error') {
const message = error instanceof ApiError
? error.message
: error instanceof NetworkError
? 'تعذّر الاتصال — تحقق من اتصالك بالإنترنت'
: 'حدث خطأ غير متوقع';
return (
<div role="alert">
<p>خطأ: {message}</p>
<button onClick={refetch}>إعادة المحاولة</button>
</div>
);
}
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
بـ async/await (أنظف)
لا يمكن جعل دالة useEffect نفسها async، فنعرّف دالة داخلية:
import { HttpClient, ApiError, NetworkError } from "./lib/react-base-repository";
// ❌ قبل Repository — fetch مباشرة في المكوّن
// ✅ بعد Repository — المكوّن يستخدم hook فقط
function useUsers_V2(): AsyncState<UserDTO[]> & { refetch: () => void } {
const result = useAsyncService(async () => {
const response = await fetch("/api/users");
if (!response.ok) throw ApiError.fromResponse(response);
return response.json();
});
useEffect(() => { result.refetch(); }, [result.refetch]);
return result;
}
إعادة الجلب عند تغيّر قيمة
import { useAsyncService } from "./lib/react-base-repository";
// عند تغيّر userId، إعادة جلب البيانات عبر Repository
function useUser(userId: string): AsyncState<UserDTO> & { refetch: () => void } {
const result = useAsyncService(() => userRepo.getUser(userId));
useEffect(() => { result.refetch(); }, [userId, result.refetch]);
return result;
}
تجنّب تحديث مكوّن مُزال (Cleanup)
import { useAsyncService } from "./lib/react-base-repository";
// useAsyncService يعالج cleanup تلقائياً — لا حاجة لـ active flag
function useSafeData(): AsyncState<DataDTO> & { refetch: () => void } {
const result = useAsyncService(() => http.get<DataDTO>("/data"));
useEffect(() => { result.refetch(); }, [result.refetch]);
return result;
}
ملاحظة: المكتبات الجاهزة
للمشاريع الكبيرة، مكتبات مثل React Query (TanStack Query) تتولّى التحميل والتخزين المؤقّت وإعادة المحاولة تلقائيًا — لكن فهم النمط اليدوي أساس مهم.
الأخطاء الشائعة
- ❌ جعل دالة useEffect نفسها
async→ عرّف دالة داخلية بدلًا منها. - ❌ نسيان حالتي التحميل والخطأ → تجربة سيئة.
- ❌ نسيان مفتاح
keyعند عرض القائمة. - ❌ تحديث الحالة بعد إزالة المكوّن → استخدم علم التنظيف.
خلاصة
اجلب البيانات بنمط useState (data/loading/error) + useEffect + fetch. اعرض حالة التحميل والخطأ، وأعد الجلب عبر الاعتماديات، ونظّف لتجنّب تحديث مكوّن مُزال. هذا أكثر أنماط React استخدامًا.