React والأطر البرمجية الحديثة للواجهات الأمامية
التعمق في الخطافات وإدارة الحالة
5 دقيقة للقراءة
خطافات React هي العمود الفقري لتطوير React الحديث. يختبر المحاورون ليس فقط قدرتك على استخدام الخطافات، بل فهمك لقواعدها ومزالقها ومتى تختار البدائل.
مزالق تنظيف 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>;
}
مصفوفة القرار
| الحاجة | الحل |
|---|---|
| المظهر، اللغة، المصادقة | React Context |
| حالة النموذج | useState / useReducer (محلي) |
| حالة محلية معقدة | useReducer |
| حالة عميل مشتركة | Zustand أو Jotai |
| بيانات الخادم | TanStack Query |
| حالة عامة واسعة النطاق | Redux Toolkit (إذا كان موجودًا في المشروع) |
نصيحة للمقابلات: عند السؤال عن إدارة الحالة، أظهر أنك تختار الأداة المناسبة للحاجة المحددة بدلاً من الاعتماد على حل واحد لكل شيء. هذا يُظهر تفكيرًا معماريًا.
التالي: سنستكشف مكونات الخادم وبنية Next.js التي تُعيد تشكيل تطوير الواجهات الأمامية. :::