خشبة منهج React — المسرح الحي · ١٣ إقليم · بلا مهلة الستار مرفوع
ملاحقتُقرأ بعد الإقليم ١٢

الملاحق — ما يبقى بعد أن تنسى التفاصيل

الشجرة كاملةً بكل وصلاتها، ثلاثة عشر نموذجاً ذهنياً، ومرجعٌ سريع للاستخدام بعد إتمام المنهج.

ملحق: شجرة المهارات الكاملة

الخريطة التي كانت مكشوفةً جزئياً في README.md، الآن مكتملةً بكل الوصلات مشروحة. كل سهمٍ هنا يعني "لا يُفهَم الابن حقّاً بلا الأب".

[تكتب TypeScript بثقة — مفترَض سلفاً] │ ┌─────────────────────────┴─────────────────────────┐ │ الإقليم ٠ — قبل أن يُرفع الستار │ │ الوصف كبياناتٍ محضة (VNode) ← المطابِق يقارن │ │ بدل يعيد البناء ← المفاتيح تحلّ مشكلة الهويّة │ └─────────────────────────┬─────────────────────────┘ │ ┌─────────────────────────┴─────────────────────────┐ │ الإقليم ١ — الممثل والسيناريو │ │ المكوّن = دالّة props→وصف ← يُستدعى حين type دالّة │ │ children = تركيب ← Fragment يحلّ قيد "قيمةٌ واحدة" │ └─────────────────────────┬─────────────────────────┘ │ ┌─────────────────────────┴─────────────────────────┐ │ الإقليم ٢ — دفتر الممثل (الأهمّ) │ │ الفيبر: سجلّ ذاكرةٍ موضعي لكل نسخة مكوّن │ │ useState = خانةٌ بالترتيب ← قواعد الهوكس مُشتقّة │ │ لا مُعطاة (لا شروط، لا حلقات، فقط أثناء رسمٍ فعلي) │ └─────────────────────────┬─────────────────────────┘ │ ┌─────────────────────────┴─────────────────────────┐ │ الإقليم ٣ — خلف الكواليس │ │ useEffect = مزامنةٌ مع تبعيات (لا "دورة حياة") │ │ تنظيفٌ يُبطل كل تشغيلٍ ← سباق تسابق (إغلاقٌ جديد │ │ لكل تشغيل) ← StrictMode يختبر اكتمال التنظيف │ └─────────────────────────┬─────────────────────────┘ │ ┌─────────────────────────┴─────────────────────────┐ │ الإقليم ٤ — فعلٌ أم تزامن؟ │ │ من يعرف سبب التنفيذ؟ حدثٌ مباشر ← معالج حدث │ │ حالةٌ متعدّدة الأسباب ← أثر. القاعدة تحسم كل تطبيق │ └───────┬─────────────────────────────────┬─────────┘ │ │ ┌───────────────────┴───────────────────┐ ┌─────────┴─────────────────┐ │ الإقليم ٥ — أدواتٌ في جيب الممثل │ │ الإقليم ٦ — روتينات مخصّصة │ │ useRef: ذاكرةٌ بلا رسم (فيبر بلا setter)│ │ دالّةٌ تستدعي هوكساً داخلها │ │ useMemo/useCallback: هويّةٌ عبر تبعيات │ │ تستعير خانات فيبر المستدعي │ │ (نفس مقارنة تبعيات الإقليم ٣) │ │ useAsync يجمع ٤+٥ معاً │ │ useReducer: انضباط انتقالٍ مركزي │ │ │ └───────────────────┬───────────────────┘ └─────────┬─────────────────┘ └────────────────┬────────────────┘ │ ┌─────────────────────────┴─────────────────────────┐ │ الإقليم ٧ — مكبّر الصوت العام │ │ Context يحلّ ألم الإقليم ١ (تمريرٌ يدوي عبر مستوياتٍ│ │ لا تستخدم القيمة) ← ثمنه: فانوتُ رسمٍ حتمي، أثبتّه │ │ بنموذج pub-sub مصغّر ← حدود الأداة: بيانات عالية │ │ التردّد لا تناسبه │ └───────┬─────────────────┬─────────────────┬───────┘ │ │ │ ┌───────────────────┴───┐ ┌──────────┴──────────┐ ┌───┴────────────────────┐ │ الإقليم ٨ — خريطة المسرح│ │ الإقليم ٩ — ورشة الأزياء│ │ الإقليم ١٠ — الجولة والحارس│ │ تنظيمٌ حسب الميزة لا │ │ CVA (تركيبة الإقليم ١ │ │ رابط المتصفّح كحالة (History│ │ النوع ← حدّ شكل/منطق │ │ مطبَّقاً على CSS) + │ │ API) ← Link يستعمل Context │ │ (خطاف الإقليم ٦ يُستهلك │ │ forwardRef (يمتدّ على │ │ (الإقليم ٧) ← Outlet = │ │ من أشكالٍ متعدّدة) + │ │ ref الإقليم ٥) + │ │ children (الإقليم ١) لكن │ │ FSD كبديلٍ أصرم │ │ asChild/Slot (لماذا │ │ مصدرها مطابقة رابطٍ ← حارسٌ │ │ │ │ الرابط الحقيقي، الإقليم │ │ = مسارٌ أبٌ شرطي │ │ │ │ ١٠) + headless │ │ │ └───────────────────┬───┘ └──────────┬──────────┘ └───┬────────────────────┘ └─────────────────┬┴─────────────────┘ │ ┌─────────────────────────┴─────────────────────────┐ │ الإقليم ١١ — تفتيش ما قبل العرض │ │ tsc يفحص الشكل؛ ESLint يفحص أنماط React الدلالية │ │ (rules-of-hooks يمسك بقّة الإقليم ٢، exhaustive-deps│ │ يمسك بقّة الإقليم ٣) ← Prettier منفصلٌ للتنسيق │ └─────────────────────────┬─────────────────────────┘ │ ┌─────────────────────────┴─────────────────────────┐ │ الإقليم ١٢ — ليلة الافتتاح │ │ كل عقدةٍ أعلاه، في تطبيقٍ واحد. لا عقدة جديدة — │ │ فقط التقاء كل ما سبق في مسارٍ متّصل واحد. │ └─────────────────────────────────────────────────────┘

نقاط الالتقاء الثلاث (حيث تتشابك خيوطٌ مستقلّة)

الالتقاء الأول — الإقليم ٤ يتفرّع، الإقليم ٧ يجمع: الإقليمان ٥ و٦ مستقلّان ظاهرياً (أدواتٌ إضافية مقابل تركيب المنطق)، لكنهما يلتقيان فعلياً في خطاف useAsync (الإقليم ٦) الذي يستعمل useCallback (الإقليم ٥) ليُثبّت هويّة run — بلا هذا الثبات، أي استهلاكٍ لـuseAsync داخل أثرٍ (الإقليم ٣) يدخل حلقةً لا نهائية. الإقليم ٧ (Context) لا يحتاج أياً منهما مباشرة، لكنه يرث كل دروس المزامنة والذاكرة التي بُنيت قبله.

الالتقاء الثاني — الإقليم ٧ يتفرّع لثلاثة اتجاهاتٍ مستقلّة تماماً: خريطة المسرح (٨، تنظيمٌ)، ورشة الأزياء (٩، مكوّناتٌ موثوقة)، والجولة والحارس (١٠، تنقّل) — الثلاثة لا يعتمد أحدها على الآخر مباشرة، لكن الإقليم ١٠ يستهلك كلاً من ٧ (Context لبثّ حالة التوجيه) و٩ (asChild لجعل روابط تبدو كأزرار) معاً في نفس اللغز — أول نقطةٍ في المنهج يتقاطع فيها ثلاثة أقاليم مستقلّة في لغزٍ واحد.

الالتقاء الثالث — الإقليم ١١ يغلق الدائرة على الإقليمين ٢ و٣ تحديداً: كل قاعدةٍ اشتُقّت يدوياً في الإقليمين ٢ (ترتيب الهوكس) و٣ (مصفوفة التبعيات) تُكتشَف الآن آلياً — لا كمعرفةٍ جديدة، بل كأداةٍ تُغني عن التكرار اليدوي لنفس عملية التشخيص التي أتقنتَها بيدك.

الثنائيّات الناظمة (تتكرّر بأشكالٍ مختلفة عبر المنهج كله)

  • صف "ماذا" لا "كيف تُحدّث": الإقليم ٠ (المطابِق) → الإقليم ١ (المكوّن كدالّةٍ من بيانات) → الإقليم ٤ (فعلٌ محدَّدٌ مقابل تزامنٍ وصفي).
  • الموضع لا الاسم: خانة الفيبر (٢) → مصفوفة التبعيات كمقارنة عنصر بعنصر (٣) → خانات useMemo/useReducer (٥) → خانات الخطاف المخصّص تُستعار من فيبر المستدعي (٦).
  • استخرج المتكرّر، وثّق مصدره الوحيد: بطاقة المسار (١) → منطق النموذج (٦) → تركيبة CSS (٩) → إطار الصفحة (١٠، Outlet).
  • الثمن معروفٌ ومُدار، لا مُخفى: الفانوت في Context (٧)، تكلفة المقارنة في useMemo (٥)، التكلفة التنظيمية لـFSD (٨) — كل أداةٍ قويّة لها ثمنٌ تعرفه بدقّة بدل أن تتجاهله.

ملحق: النماذج الذهنية

ثلاثة عشر نموذجاً، كلٌّ يختزل إقليماً في جملةٍ تشتقّ منها الباقي. هذه ما يبقى بعد أن تنسى التفاصيل.

٠. صف "ماذا"، ودَع مطابِقاً يقارن بدل أن يبني من جديد

الوصف بياناتٌ محضة (شجرة نوع/خصائص/أبناء)؛ التحديث اليدوي للـDOM يُنتج نسختين من الحقيقة تتباعدان. المطابِق يقارن القديم بالجديد ويُطبّق الفرق الأدنى فقط. القوائم تحتاج هويّةً صريحة (key) لا موضعاً، وإلا اختلطت الحالة بين العناصر عند إعادة الترتيب. (٠)

١. المكوّن دالّةٌ من props إلى وصف؛ children سيناريوٌ خاص

تكرار الشكل يُستخرَج لدالّةٍ واحدة تأخذ بيانات، كأي دالّةٍ عادية. props تتدفّق لأسفل فقط — تحويرها مباشرةً يُعيد نفس تباعد الإقليم ٠. children ليست سحراً، فقط prop يملؤه محتوى ما بين وسمي الفتح والإغلاق تلقائياً. (١)

٢. الذاكرة موضعيةٌ في سجلٍّ خارج الدالّة، لا داخلها

الدالّة تُستدعى من الصفر كل رسم؛ الذاكرة تعيش في فيبر — سجلٌّ دائم لكل نسخة مكوّن. useState خانةٌ برقم ترتيب النداء، لا بالاسم. قواعد الهوكس (ترتيبٌ وعددٌ ثابتان، فقط أثناء رسمٍ فعلي) نتيجةٌ مباشرة لهذه الآلية، لا اصطلاحٌ تعسّفي. (٢، أهمّ إقليمٍ في المنهج)

٣. الأثر يُزامن مع تبعيات، لا يتبع "دورة حياة"

الرسم يجب أن يبقى نقياً؛ أي لمسٍ للعالم الخارجي ينتقل لـuseEffect، يعمل بعد استقرار الرسم. مصفوفة التبعيات عقدُ صحّة لا تحسينٌ اختياري. كل تشغيلٍ له تنظيفه الخاص، بإغلاقٍ مستقل — وهنا تُحلّ سباقات التسابق. StrictMode يشغّل مرّتين عمداً ليختبر اكتمال هذا التنظيف. (٣)

٤. من يعرف سبب التنفيذ يحدّد الأداة

سببيّةٌ مباشرة ومعروفة (نقرة) → معالج حدث، يُستدعى فقط حين يقع الفعل، بلا مقارنةٍ بقيمةٍ سابقة. مزامنةٌ مع حالةٍ متعدّدة الأسباب → أثر. خلطهما (علمٌ يُراقَب بأثر بدل فعلٍ مباشر) يُنتج بقّاتٍ تبدو صحيحة حتى تنكسر صامتة. (٤)

٥. أدواتٌ متخصّصة، أساسٌ واحد (نفس سجلّ الفيبر)

useRef: خانةٌ بكائنٍ ثابتٍ يُحوَّر في مكانه، بلا طلب رسم. useMemo/ useCallback: خانةٌ بقيمةٍ وتبعياتها، بنفس منطق مقارنة الإقليم ٣، لثبات الهويّة المرجعية. useReducer: نفس useState، بانضباط انتقالٍ مركزيٍّ عبر دالّةٍ نقيّة بدل setters مستقلّة. (٥)

٦. الخطاف المخصّص يستعير خانات المستدعي، لا يملك سجلّه الخاص

دالّةٌ عادية تستدعي هوكساً داخلها، فتخضع لنفس قواعد الترتيب عبر حدود الدوال. تُعيد استخدام السلوك كما يُعيد المكوّن استخدام الشكل. اسم use إشارةٌ لأدواتٍ لاحقة (الإقليم ١١) أن هذا يستدعي هوكساً بداخله. (٦)

٧. مكبّر الصوت العام: بثٌّ للجميع، لا اشتراكٌ انتقائي

Context يحلّ تمرير props عبر مستوياتٍ لا تستخدمها. الثمن: أي تغييرٍ في القيمة يُعيد رسم كل مستهلكٍ، بصرف النظر عمّا يهمّه فعلاً — لأن التخزين كتلةٌ واحدة، لا حقولاً منفصلة الاشتراك. الحلّ: قنواتٌ منفصلة بمعدّل التغيّر، لا كتلةٌ واحدة تخلط النادر بالمتكرّر. (٧)

٨. نظّم حسب "من يملك"، لا "ما شكله"

تجميعٌ حسب الميزة (لا نوع الملف) يحلّ تصادم الفرق بنيوياً. حدٌّ بين "شكل" (يستقبل ويعرض) و"منطق" (خطافٌ ينتج) يسمح بإعادة استخدام نفس البيانات بأشكالٍ متعدّدة. الترقية إلى shared بعد أول استخدامٍ فعلي في قسمين، لا تخميناً مسبقاً. (٨)

٩. الشكل من دالّة variants، السلوك من ref يعبر الحاجز، الهويّة تُمنَح لا تُفرَض

cva: خانة "حالاتٍ أساسية" مكتوبةٌ مرّةً تُطبَّق على كل تركيبة، تمنع بقّة النسيان بنيوياً. forwardRef: ref لا تصل لـprops تلقائياً، يحتاج توقيعاً خاصاً. asChild/Slot: امنح شكلك وسلوكك لعنصرٍ آخر بدل فرض غلافك عليه — يحلّ "أريد شيئاً يبدو كذا لكنه ليس ذاك العنصر". (٩)

١٠. الرابط حالةٌ مشتركة عبر History API، لا مجرّد نصّ في الشريط

window.location.href يُعيد تحميل الصفحة بالكامل؛ pushState يغيّر الرابط بلا ذلك، لكنه لا يُطلق popstate بنفسه — تحتاج مزامنةً يدوية صريحة. Outlet = children، لكن مصدره مطابقة رابطٍ لا تركيبٌ صريح في JSX. الحارس مسارٌ أبٌ شرطي: يمرّر (Outlet) أو يحوّل. (١٠)

١١. فحص الشكل يختلف عن فحص الأنماط الدلالية عن فحص التنسيق

tsc يتحقّق من توافق الأنواع فقط — بقّاتٌ كترتيب الهوكس ومصفوفات التبعيات الناقصة نوعياً صحيحة تماماً، فلا يمسكها. ESLint (بإضافاتٍ خاصّة بـReact) يفهم أنماط الاستخدام الدلالية. Prettier منفصلٌ تماماً — قرارٌ حتميٌّ بلا نقاش، وeslint-config-prettier يمنع تعارضهما. (١١)

١٢. كل عقدةٍ سابقة، في مسارٍ متّصلٍ واحد

لا نموذج جديد هنا — فقط إثبات أن الأسماء الثلاثة عشر أعلاه ليست معرفةً منفصلة، بل شبكةٌ واحدة تتحرّك خلالها كل نقرة، من الحدث حتى استقرار البكسل على الشاشة. (١٢)

ملحق: مرجعٌ سريع

مرجعٌ للاستخدام بعد إتمام المنهج — ليس مادّةً تعليمية. لو احتجتَ فهم *لماذا* سطرٍ هنا، ارجع لإقليمه المذكور بين قوسين.

الخطافات — إشارةٌ سريعة

الخطافيُرجعالغرض بجملةإقليم
useState(initial)[value, setValue]ذاكرةٌ تُطلق رسماً جديداً عند التغيّر٢
useEffect(fn, deps)مزامنةٌ بعد الرسم؛ fn قد تُعيد دالّة تنظيف٣
useRef(initial){ current } (ثابتٌ)ذاكرةٌ بلا رسمٍ جديد؛ توصيلٌ لعقد DOM٥
useMemo(compute, deps)قيمةٌ محفوظةيُعيد نفس المرجع إن لم تتغيّر deps٥
useCallback(fn, deps)نفس fn (ثابتة)useMemo(() => fn, deps) — حالةٌ خاصّة منه٥
useReducer(reducer, init)[state, dispatch]useState بانضباط انتقالٍ مركزي٥
useContext(Ctx)القيمة الحاليةالتقاط قيمةٍ مبثوثة من أقرب Provider فوقه٧

قاعدة الاعتماديات (تنطبق على useEffect/useMemo/useCallback): أي قيمةٍ من خارج الدالّة تُقرَأ بداخل الكولباك يجب أن تكون في المصفوفة. [] = مرّةً واحدة. بلا مصفوفة أصلاً = كل رسم (نادراً ما تريد هذا).

قواعد الهوكس (كلاهما مُشتقّتان من آلية الفيبر الموضعية، الإقليم ٢): ١) نادِها فقط في المستوى الأعلى من مكوّنٍ أو خطافٍ مخصّص — لا شرط، لا حلقة، لا دالّة متداخلة. ٢) نادِها فقط من مكوّنات React أو خطافاتٍ تبدأ بـuse.

نمط الـvariant (على طراز CVA)

import { cva, type VariantProps } from "class-variance-authority";

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium transition-colors " +
  "focus-visible:ring-2 disabled:opacity-50 disabled:pointer-events-none",   // الحالات الخمس، مرّةً واحدة
  {
    variants: {
      variant: { primary: "bg-primary text-white", outline: "border" },
      size: { sm: "h-8 px-3", md: "h-10 px-4", lg: "h-12 px-6" },
    },
    defaultVariants: { variant: "primary", size: "md" },
  },
);

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
  VariantProps<typeof buttonVariants> & { asChild?: boolean };

نمط forwardRef + asChild معاً

import { forwardRef } from "react";
import { Slot } from "@radix-ui/react-slot";   // أو ما يكافئه في base-ui

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ asChild, variant, size, className, ...props }, ref) => {
    const Comp = asChild ? Slot : "button";
    return (
      <Comp ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...props} />
    );
  },
);
Button.displayName = "Button";

نمط Context بخطاف حارسٍ (throw خارج الـProvider)

type ThemeContextValue = { theme: "dark" | "light"; setTheme: (t: "dark" | "light") => void };
const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<"dark" | "light">("dark");
  const value = useMemo(() => ({ theme, setTheme }), [theme]);   // تثبيت الهويّة — الإقليم ٧
  return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}

export function useTheme(): ThemeContextValue {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error("useTheme يجب أن يُستدعى داخل ThemeProvider");
  return ctx;
}

قاعدة الفانوت: قنواتٌ منفصلة حسب معدّل التغيّر. لا تجمع قيمةً نادرة التغيّر (ثيم) مع قيمةٍ عالية التردّد (عدّاد حيّ) في نفس الـContext.

نمط الموجّه + الحارس

import { createBrowserRouter, RouterProvider, Outlet, Navigate, useLocation } from "react-router-dom";

function RequireAuth() {
  const { isAuthenticated } = useAuth();               // من الإقليم ٧
  const location = useLocation();
  if (!isAuthenticated) return <Navigate to="/login" state={{ from: location }} replace />;
  return <Outlet />;
}

const router = createBrowserRouter([
  {
    element: <MainLayout />,                            // Navbar/Footer مرّةً واحدة
    children: [
      { path: "/", element: <HomePage /> },
      {
        element: <RequireAuth />,                        // طبقة حراسة
        children: [
          { path: "/profile", element: <ProfilePage /> },
        ],
      },
    ],
  },
]);

eslint.config.js — هيكلٌ جاهزٌ للنسخ

import js from "@eslint/js";
import tseslint from "typescript-eslint";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import prettier from "eslint-config-prettier";

export default tseslint.config(
  { ignores: ["dist"] },
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    files: ["**/*.{ts,tsx}"],
    plugins: { "react-hooks": reactHooks, "react-refresh": reactRefresh },
    rules: {
      ...reactHooks.configs.recommended.rules,          // rules-of-hooks + exhaustive-deps
      "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
    },
  },
  prettier,                                               // ⚠️ إلزامياً آخر عنصر
);

الحزم: npm i -D eslint typescript-eslint eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-config-prettier prettier

جدول تشخيص الأعراض (نسخةٌ دائمة من لغز الإقليم ١٢)

العرضالإقليمالآلية
زرٌّ يعمل مرّةً، يموت بعد فشلةٍ واحدة٤علمٌ لم يتغيّر = React لا يُعيد الرسم = الأثر لا يعمل
نتائج بحثٍ قديمة تظهر متأخّرة٣سباق تسابق — رد بطيءٌ وصل بعد رد أسرع
قيمة حقلٍ "تقفز" بعد إدراج عنصرٍ في قائمة٠مطابقةٌ بالموضع لا بمفتاح
مكوّنٌ يُعاد رسمه لتغييرٍ لا يخصّه٧فانوت Context — قناةٌ واحدة لبياناتٍ مختلفة التردّد
أثرٌ يطبع قيمةً قديمة دائماً٣إغلاقٌ عالق — تبعيةٌ ناقصة
ref على مكوّنٍ مخصّص لا يتّصل٩نسي forwardRef
عنصرين متداخلين بدل واحد٩نسي asChild/Slot
صفحةٌ محميّة تظهر للحظة قبل التحويل١٠الحارس يعمل بعد الرسم الأول لا قبله
حالةٌ غير متّسقة بعد نقراتٍ متتابعة سريعة٥useState مستقلّة متعدّدة بدل useReducer مركزي
tsc نظيفٌ لكن حلقة طلباتٍ لا نهائية٣fetch/أثرٌ جانبي في جسم الرسم مباشرة، لا في useEffect
هوكٌ يُفسد خاناتٍ أخرى أحياناً٢نداءٌ شرطي يكسر ترتيب الفيبر

أوامر شائعة

npm run dev              # خادم تطوير Vite
npm run build             # tsc -b && vite build
npx eslint . --fix        # فحص + إصلاحٌ آلي لما يُصلَح آلياً
npx prettier --write .    # تنسيقٌ آلي كامل