React والأطر البرمجية الحديثة للواجهات الأمامية

التعمق في الخطافات وإدارة الحالة

5 دقيقة للقراءة

خطافات 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

اعرف متى تستخدم كلاً منهما:

الميزةuseStateuseRef
يُحفّز إعادة العرضنعملا
يستمر عبر العروضنعمنعم
متاح في العرضالقيمة الحالية.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 التي تُعيد تشكيل تطوير الواجهات الأمامية. :::

اختبار

الوحدة 3: React والأطر البرمجية الحديثة

خذ الاختبار
نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.