React Hooks من الصفر — دليل عملي شامل
React4 دقيقة للقراءة

React Hooks من الصفر — دليل عملي شامل

فهم عميق لأشهر React Hooks: useState، useEffect، useCallback، useMemo، وuseRef — متى تستخدم كل واحد ومتى تتجنبه.

لماذا Hooks؟

قبل React 16.8، كانت الحالة والدورة الحياتية حكرًا على Class Components. الـ Hooks غيّرت هذا كليًا — الآن كل شيء ممكن في functional components، بكود أقل وأوضح.


useState — الحالة المحلية

الأساسي

TSX
import { useState } from "react";
 
function Counter() {
  const [count, setCount] = useState(0);
 
  return (
    <button onClick={() => setCount(count + 1)}>
      العدد: {count}
    </button>
  );
}

التحديث بناءً على القيمة السابقة

TSX
// خطأ — قد تقرأ قيمة قديمة
setCount(count + 1);
 
// صح — يضمن القيمة الأحدث دائمًا
setCount((prev) => prev + 1);

استخدم دائمًا صيغة الدالة عند التحديث المتتالي أو داخل setTimeout و setInterval.

حالة الكائنات

TSX
const [user, setUser] = useState({ name: "أحمد", age: 25 });
 
// لا تنسَ spread — useState لا يدمج تلقائيًا
setUser((prev) => ({ ...prev, age: 26 }));

useEffect — التأثيرات الجانبية

متى يعمل؟

التبعياتمتى يعمل
بدون مصفوفةبعد كل render
[] فارغةمرة واحدة عند الـ mount
[a, b]عند تغيّر a أو b
TSX
useEffect(() => {
  document.title = `${count} إشعار جديد`;
}, [count]);

تنظيف التأثيرات

TSX
useEffect(() => {
  const controller = new AbortController();
 
  fetch("/api/data", { signal: controller.signal })
    .then((res) => res.json())
    .then(setData)
    .catch((err) => {
      if (err.name !== "AbortError") console.error(err);
    });
 
  // cleanup — يُشغَّل قبل تشغيل التأثير مجددًا أو عند unmount
  return () => controller.abort();
}, []);

إذا كنت تجلب بيانات في useEffect، فكّر في استخدام TanStack Query أو SWR بدلًا من ذلك — يحلان كثيرًا من حالات الحافة تلقائيًا.


useCallback — تثبيت الدوال

TSX
// مشكلة: الدالة تتغير في كل render
function Parent() {
  const [count, setCount] = useState(0);
 
  // هذه الدالة كائن جديد في كل render
  const handleClick = () => console.log("clicked");
 
  return <Child onClick={handleClick} />;
}
 
// حل: useCallback يُبقي نفس مرجع الدالة
function Parent() {
  const [count, setCount] = useState(0);
 
  const handleClick = useCallback(() => {
    console.log("clicked");
  }, []); // لا تعتمد على شيء
 
  return <Child onClick={handleClick} />;
}

useCallback مفيد فقط عند تمرير الدوال إلى مكونات محسّنة بـ React.memo. في غير ذلك قد يضيف تعقيدًا بلا فائدة.


useMemo — تخزين النتائج

TSX
function ProductList({ products, filter }: Props) {
  // يُحسب مجددًا فقط عند تغيّر products أو filter
  const filtered = useMemo(
    () => products.filter((p) => p.category === filter),
    [products, filter]
  );
 
  return <ul>{filtered.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}

متى تستخدم useMemo؟

TSX
// لا تحتاجه — العملية رخيصة
const doubled = useMemo(() => count * 2, [count]);
 
// تحتاجه — ترتيب وتصفية مصفوفة كبيرة
const sorted = useMemo(
  () => [...items].sort((a, b) => a.price - b.price),
  [items]
);

useRef — المرجع الثابت

له استخدامان مختلفان تمامًا:

١. الوصول لعناصر DOM

TSX
function AutoFocus() {
  const inputRef = useRef<HTMLInputElement>(null);
 
  useEffect(() => {
    inputRef.current?.focus();
  }, []);
 
  return <input ref={inputRef} placeholder="سيُركّز تلقائيًا" />;
}

٢. قيمة لا تُسبّب إعادة render

TSX
function Stopwatch() {
  const [elapsed, setElapsed] = useState(0);
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
 
  const start = () => {
    intervalRef.current = setInterval(() => {
      setElapsed((e) => e + 1);
    }, 1000);
  };
 
  const stop = () => {
    if (intervalRef.current) clearInterval(intervalRef.current);
  };
 
  return (
    <div>
      <p>{elapsed} ثانية</p>
      <button onClick={start}>ابدأ</button>
      <button onClick={stop}>أوقف</button>
    </div>
  );
}

Hook مخصص — Custom Hook

الـ Custom Hooks هي الطريقة الصحيحة لمشاركة المنطق بين المكونات:

TSX
function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : initial;
    } catch {
      return initial;
    }
  });
 
  const set = useCallback(
    (newValue: T) => {
      setValue(newValue);
      localStorage.setItem(key, JSON.stringify(newValue));
    },
    [key]
  );
 
  return [value, set] as const;
}
 
// الاستخدام
function Settings() {
  const [theme, setTheme] = useLocalStorage("theme", "light");
 
  return (
    <select value={theme} onChange={(e) => setTheme(e.target.value)}>
      <option value="light">فاتح</option>
      <option value="dark">داكن</option>
    </select>
  );
}