ما هي الخطّافات المخصّصة؟
دالة تبدأ بـ use وتستخدم خطّافات React بداخلها، لتغليف منطق قابل لإعادة الاستخدام ومشاركته بين المكوّنات. تحلّ مشكلة تكرار نفس منطق الحالة/التأثير في عدة أماكن.
القاعدة الذهبية
🔑 اسم الخطّاف المخصّص يجب أن يبدأ بـ
use(مثلuseToggle)، حتى تطبّق عليه React قواعد الخطّافات.
مثال 1: useToggle
import { useState } from "react";
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue((v) => !v);
return [value, toggle];
}
// الاستخدام:
function Modal() {
const [open, toggleOpen] = useToggle();
return (
<>
<button onClick={toggleOpen}>{open ? "إغلاق" : "فتح"}</button>
{open && <div>المحتوى</div>}
</>
);
}
مثال 2: useLocalStorage
يزامن الحالة مع تخزين المتصفح:
function useLocalStorage(key, initial) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initial;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// الاستخدام:
const [name, setName] = useLocalStorage("name", "");
مثال 3: useAsyncService (مع Repository)
يغلّف منطق جلب البيانات عبر Repository — لا fetch مباشر في الـ Hook أو المكوّن:
import { useState, useEffect, useCallback } from "react";
import { ApiError, NetworkError, UserDTO, HttpClient, HttpUserRepository } from "./lib/react-base-repository";
// حالة عامة لجميع الخدمات
type AsyncState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: ApiError | NetworkError | Error };
// Service Hook العام — يستقبل Repository method وليس fetch
function useAsyncService<T>(
fetcher: () => Promise<T>,
deps: unknown[] = [],
): AsyncState<T> & { refetch: () => void } {
const [state, setState] = useState<AsyncState<T>>({ status: "idle" });
const execute = useCallback(() => {
setState({ status: "loading" });
fetcher()
.then((data) => setState({ status: "success", data }))
.catch((error) => setState({ status: "error", error }))
.finally(() => {});
}, deps);
useEffect(() => { execute(); }, [execute]);
return { ...state, refetch: execute };
}
// الاستخدام مع Repository — لا fetch مباشراً
const http = new HttpClient("/api");
const userRepo = new HttpUserRepository(http);
function Users() {
const { status, data: users, error, refetch } = useAsyncService<UserDTO[]>(
() => userRepo.getUsers(),
[],
);
if (status === "loading") return <p>تحميل...</p>;
if (status === "error") return <p role="alert">{error.message}</p>;
return <ul>{data.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
نفس المنطق، سطر واحد، بلا تكرار!
الأخطاء الشائعة
- ❌ نسيان بدء الاسم بـ
use→ قواعد الخطّافات لا تُطبّق. - ❌ استدعاء الخطّافات شرطيًا داخل الخطّاف المخصّص → خالف قواعد الخطّافات.
- ❌ تغليف منطق لا يتكرّر → الخطّافات المخصّصة لإعادة الاستخدام.
خلاصة
الخطّافات المخصّصة دوال تبدأ بـ use تغلّف منطقًا قابلًا لإعادة الاستخدام (toggle, fetch, localStorage). تجعل مكوّناتك نظيفة وخالية من التكرار — إحدى أقوى أدوات React الحديثة.