تخطَّ إلى المحتوى

⚛️ شرح React

الخطّافات المخصّصة (Custom Hooks)

الدرس 15 من 32· ⏱ 2 دقائق قراءة· 🗓 آخر تحديث: ١٧ يوليو ٢٠٢٦

ما هي الخطّافات المخصّصة؟

دالة تبدأ بـ 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 الحديثة.

شرح الخطّافات المخصّصة (Custom Hooks) — React بالعربي
الخطّافات المخصّصة (Custom Hooks)React بالعربي · The Code Fix

📚 لمزيد من التعمّق في React، راجِع التوثيق الرسمي لـ React.

هل كان هذا الدرس مفيدًا؟