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>
);
}