React والأطر البرمجية الحديثة للواجهات الأمامية
التعمق في الخطافات وإدارة الحالة
خطافات React هي العمود الفقري لتطوير React الحديث. يختبر المحاورون ليس فقط قدرتك على استخدام الخطافات، بل فهمك لقواعدها ومزالقها ومتى تختار البدائل. قواعد كود React الحديثة (React 18+/19) نادرًا ما تستخدم مكونات الفئات أو دوال دورة الحياة القديمة (componentDidMount, componentDidUpdate) — توقع أن تركز المقابلات بشكل شبه كامل على الكود المبني على الخطافات.
مزالق تنظيف useEffect
المصدر الأكثر شيوعًا للأخطاء في مقابلات React:
// خطأ — تسرب ذاكرة، بدون تنظيف
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// تنظيف مفقود! الفاصل الزمني يعمل للأبد
}, []);
// صحيح — تنظيف عند فك التركيب
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
// خطأ — إغلاق قديم على الحالة
useEffect(() => {
const handler = () => {
console.log(count); // دائمًا يسجل القيمة الأولية
};
window.addEventListener('scroll', handler);
return () => window.removeEventListener('scroll', handler);
}, []); // deps فارغ = إغلاق قديم
// صحيح — استخدام ref لأحدث قيمة
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
const handler = () => {
console.log(countRef.current); // دائمًا حالي
};
window.addEventListener('scroll', handler);
return () => window.removeEventListener('scroll', handler);
}, []);
useRef مقابل useState
اعرف متى تستخدم كلاً منهما:
| الميزة | useState | useRef |
|---|---|---|
| يُحفّز إعادة العرض | نعم | لا |
| يستمر عبر العروض | نعم | نعم |
| متاح في العرض | القيمة الحالية | .current |
| يُستخدم لـ | حالة واجهة المستخدم، القيم المعروضة | المؤقتات، مراجع DOM، القيم السابقة، الأعلام القابلة للتغيير |
// سؤال مقابلة كلاسيكي: تنفيذ usePrevious
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<p>الآن: {count}، قبل: {prevCount}</p>
);
}
الخطافات المخصصة
تصميم خطافات مخصصة يُظهر مهارات React على مستوى كبير:
// useDebounce: تأخير قيمة تتغير بسرعة
function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// useLocalStorage: حفظ الحالة في localStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// الاستخدام
function SearchInput() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 500);
const [history, setHistory] = useLocalStorage('search-history', []);
useEffect(() => {
if (debouncedQuery) {
fetchResults(debouncedQuery);
setHistory(prev => [...prev, debouncedQuery].slice(-10));
}
}, [debouncedQuery]);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
إدارة الحالة: متى تستخدم ماذا
هذا موضوع نقاش شائع في المقابلات:
React Context
// جيد لـ: المظهر، اللغة، حالة المصادقة (تحديثات نادرة)
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
);
}
// سيئ لـ: الحالة المتغيرة بشكل متكرر (يسبب إعادة عرض جميع المستهلكين)
Zustand (مخزن خارجي خفيف)
// جيد لـ: الحالة المشتركة التي تتحدث بشكل متكرر، API بسيط
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
reset: () => set({ count: 0 }),
}));
function Counter() {
// يُعاد العرض فقط عند تغيير count، وليس قيم المخزن الأخرى
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
return <button onClick={increment}>{count}</button>;
}
TanStack Query (حالة الخادم)
// جيد لـ: جلب بيانات API، التخزين المؤقت، المزامنة
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
staleTime: 5 * 60 * 1000, // يعتبر طازجًا لمدة 5 دقائق
});
if (isLoading) return <Skeleton />;
if (error) return <Error message={error.message} />;
return <div>{data.name}</div>;
}
سؤال المتابعة الذي ينبغي توقّعه بعد وصف Context: «ولمَ لا نستخدم Context لكل شيء؟» الإجابة آلية محددة لا تفضيل شخصي.
Context مقابل مخزن خارجي — آلية إعادة العرض
React Context
- بلا اعتمادية ولا مخزن تُعدّه، وهو الإجابة التي يتوقعها المحاورون للسِّمة واللغة وحالة المصادقة
- حدود المزوّد ظاهرة في الشجرة، فمصدر القيمة واضح عند قراءة الكود
- يعمل دون تغيير داخل أشجار مكونات الخادم ما دام المزوّد نفسه مكون عميل
- أي قيمة سياق جديدة تُعيد عرض كل مستهلك بغض النظر عن الحقل الذي يقرأه — لا يوجد مُحدِّد (selector)، فـ `{user, theme, setTheme}` تُعيد عرض شارة المستخدم عند تبديل السِّمة
- الحل الشائع هو التقسيم إلى عدة سياقات، وهو يضاعف المزوّدين وينقل المشكلة بدل حلّها
- الكائن أو الدالة السهمية المُمرَّرة مباشرة في `value` هي مرجع جديد في كل عرض للأب، فيُعيد المزوّد عرض المستهلكين حتى دون تغيّر شيء
مخزن خارجي (Zustand أو Jotai أو Redux Toolkit)
- الاشتراك بمُحدِّد يعني أن المكون يُعاد عرضه فقط حين تتغير الشريحة التي يقرأها — وهذا هو السبب الحقيقي للجوء إليه
- يمكن قراءة الحالة والكتابة إليها خارج شجرة React، فتُستخدم من معالجات الأحداث والعمّال والاختبارات دون عرض أي شيء
- لا تعشيش للمزوّدين، فتبقى الشجرة مسطحة مع تزايد مجالات الحالة
- الحالة العامة القابلة للتعديل يسهل الكتابة إليها من أي مكان، وهكذا بالضبط تصير صعبة التتبّع
- المُحدِّد الذي يُرجع كائنًا جديدًا في كل استدعاء يُبطل الآلية كلها — وهو أشهر خطأ، ويبدو وكأن المكتبة معطلة
- الفرق تضع فيه بيانات الخادم بشكل روتيني ثم تكتب يدويًا تخزينًا مؤقتًا وإبطالًا وإعادة جلب تقوم بها TanStack Query أصلًا بشكل صحيح
مصفوفة القرار
| الحاجة | الحل |
|---|---|
| المظهر، اللغة، المصادقة | React Context |
| حالة النموذج | useState / useReducer (محلي) |
| حالة محلية معقدة | useReducer |
| حالة عميل مشتركة | Zustand أو Jotai |
| بيانات الخادم | TanStack Query |
| حالة عامة واسعة النطاق | Redux Toolkit (إذا كان موجودًا في المشروع) |
نصيحة للمقابلات: عند السؤال عن إدارة الحالة، أظهر أنك تختار الأداة المناسبة للحاجة المحددة بدلاً من الاعتماد على حل واحد لكل شيء. هذا يُظهر تفكيرًا معماريًا.
التالي: سنستكشف مكونات الخادم وبنية Next.js التي تُعيد تشكيل تطوير الواجهات الأمامية. :::
سجّل الدخول للتقييم