React📅 2026-10-19⏱ 13 دقائق قراءة⚛️ مقال 10 من 10

مشروع React متكامل — تطبيق متجر إلكتروني 2026

مشروع عملي شامل لبناء تطبيق متجر إلكتروني بـ React و TypeScript — مع السلة، البحث، التصفية، والحفظ التلقائي

🎉 مبروك! وصلت إلى المقال الأخير في مسار React!

لقد تعلمت كل الأساسيات: JSX، المكونات، State، الأحداث، useEffect، القوائم، Router، و Context API. الآن حان الوقت لتطبيق كل هذه المفاهيم في مشروع حقيقي متكامل.

في هذا الدليل العملي، سنبني متجراً إلكترونياً كاملاً بـ React و TypeScript.

ما سنبنيه

متجر إلكتروني بوظائف:

  • عرض المنتجات في شبكة أنيقة.
  • البحث في المنتجات.
  • التصفية حسب الفئة والسعر.
  • سلة التسوق مع إضافة/حذف.
  • صفحة تفاصيل المنتج.
  • حفظ السلة في localStorage.
  • عدد المنتجات في السلة (Header).
  • إجمالي السعر مع الضريبة.

هيكل المشروع

react-shop/
├── src/
│   ├── components/
│   │   ├── Header.tsx
│   │   ├── ProductCard.tsx
│   │   ├── ProductGrid.tsx
│   │   ├── SearchBar.tsx
│   │   ├── Filters.tsx
│   │   └── CartSidebar.tsx
│   ├── contexts/
│   │   └── CartContext.tsx
│   ├── pages/
│   │   ├── Home.tsx
│   │   ├── ProductDetail.tsx
│   │   └── NotFound.tsx
│   ├── data/
│   │   └── products.ts
│   ├── types/
│   │   └── index.ts
│   ├── App.tsx
│   ├── main.tsx
│   └── index.css

الخطوة 1: إنشاء المشروع

npm create vite@latest react-shop -- --template react-ts
cd react-shop
npm install
npm install react-router-dom
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

الخطوة 2: إعداد Tailwind CSS

tailwind.config.js:

export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: { extend: {} },
  plugins: [],
};

src/index.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  font-family: 'Cairo', sans-serif;
  direction: rtl;
  background: #f9fafb;
}

الخطوة 3: تعريف الأنواع

src/types/index.ts:

export interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  image: string;
  category: string;
  rating: number;
  inStock: boolean;
}

export interface CartItem extends Product {
  quantity: number;
}

export type Category = "الكل" | "إلكترونيات" | "ملابس" | "كتب" | "منزل";

الخطوة 4: بيانات المنتجات

src/data/products.ts:

import { Product } from "../types";

export const products: Product[] = [
  {
    id: 1,
    name: "لابتوب Dell XPS 15",
    description: "لابتوب احترافي بشاشة 15 بوصة ومعالج i7",
    price: 5500,
    image: "https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=400",
    category: "إلكترونيات",
    rating: 4.8,
    inStock: true,
  },
  {
    id: 2,
    name: "iPhone 15 Pro",
    description: "أحدث هاتف من Apple بكاميرا احترافية",
    price: 4200,
    image: "https://images.unsplash.com/photo-1592750475338-74b7b21085ab?w=400",
    category: "إلكترونيات",
    rating: 4.9,
    inStock: true,
  },
  {
    id: 3,
    name: "قميص قطني",
    description: "قميص رجالي قطني 100%",
    price: 120,
    image: "https://images.unsplash.com/photo-1596755094514-f87e34085b2c?w=400",
    category: "ملابس",
    rating: 4.5,
    inStock: true,
  },
  {
    id: 4,
    name: "كتاب تعلم React",
    description: "دليل شامل لتعلم React من الصفر",
    price: 85,
    image: "https://images.unsplash.com/photo-1544947950-fa07a98d237f?w=400",
    category: "كتب",
    rating: 4.7,
    inStock: true,
  },
  {
    id: 5,
    name: "مصباح طاولة",
    description: "مصباح حديث بتصميم أنيق",
    price: 250,
    image: "https://images.unsplash.com/photo-1507473885765-e6ed057f782c?w=400",
    category: "منزل",
    rating: 4.3,
    inStock: false,
  },
  {
    id: 6,
    name: "سماعات Sony WH-1000XM5",
    description: "سماعات لاسلكية مع إلغاء الضوضاء",
    price: 1800,
    image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400",
    category: "إلكترونيات",
    rating: 4.9,
    inStock: true,
  },
  {
    id: 7,
    name: "حقيبة جلد",
    description: "حقيبة ظهر جلدية عملية",
    price: 450,
    image: "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400",
    category: "ملابس",
    rating: 4.6,
    inStock: true,
  },
  {
    id: 8,
    name: "ساعة ذكية",
    description: "ساعة ذكية بميزات صحية متقدمة",
    price: 1200,
    image: "https://images.unsplash.com/photo-1546868871-7041f2a55e12?w=400",
    category: "إلكترونيات",
    rating: 4.4,
    inStock: true,
  },
];

الخطوة 5: Cart Context

src/contexts/CartContext.tsx:

import { createContext, useContext, useEffect, useState, ReactNode } from "react";
import { CartItem, Product } from "../types";

interface CartContextType {
  items: CartItem[];
  addItem: (product: Product) => void;
  removeItem: (id: number) => void;
  updateQuantity: (id: number, quantity: number) => void;
  clearCart: () => void;
  total: number;
  itemCount: number;
}

const CartContext = createContext<CartContextType | undefined>(undefined);

export function CartProvider({ children }: { children: ReactNode }) {
  const [items, setItems] = useState<CartItem[]>(() => {
    try {
      const stored = localStorage.getItem("cart");
      return stored ? JSON.parse(stored) : [];
    } catch {
      return [];
    }
  });

  useEffect(() => {
    localStorage.setItem("cart", JSON.stringify(items));
  }, [items]);

  const addItem = (product: Product) => {
    setItems((prev) => {
      const existing = prev.find((item) => item.id === product.id);
      if (existing) {
        return prev.map((item) =>
          item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item
        );
      }
      return [...prev, { ...product, quantity: 1 }];
    });
  };

  const removeItem = (id: number) => {
    setItems((prev) => prev.filter((item) => item.id !== id));
  };

  const updateQuantity = (id: number, quantity: number) => {
    if (quantity <= 0) {
      removeItem(id);
      return;
    }
    setItems((prev) =>
      prev.map((item) => (item.id === id ? { ...item, quantity } : item))
    );
  };

  const clearCart = () => setItems([]);

  const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);

  return (
    <CartContext.Provider
      value={{ items, addItem, removeItem, updateQuantity, clearCart, total, itemCount }}
    >
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  const context = useContext(CartContext);
  if (!context) throw new Error("useCart must be used within CartProvider");
  return context;
}

الخطوة 6: Header مع السلة

src/components/Header.tsx:

import { Link } from "react-router-dom";
import { useCart } from "../contexts/CartContext";

interface HeaderProps {
  onToggleCart: () => void;
}

export function Header({ onToggleCart }: HeaderProps) {
  const { itemCount } = useCart();

  return (
    <header className="bg-white shadow-sm sticky top-0 z-40">
      <div className="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
        <Link to="/" className="text-2xl font-bold text-gray-900">
          🛒 متجري
        </Link>

        <button
          onClick={onToggleCart}
          className="relative bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition"
        >
          السلة 🛍️
          {itemCount > 0 && (
            <span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs w-6 h-6 rounded-full flex items-center justify-center font-bold">
              {itemCount}
            </span>
          )}
        </button>
      </div>
    </header>
  );
}

الخطوة 7: ProductCard

src/components/ProductCard.tsx:

import { Link } from "react-router-dom";
import { Product } from "../types";
import { useCart } from "../contexts/CartContext";

export function ProductCard({ product }: { product: Product }) {
  const { addItem } = useCart();

  return (
    <div className="bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-lg transition border border-gray-100 group">
      <Link to={`/product/${product.id}`}>
        <div className="relative h-48 overflow-hidden">
          <img
            src={product.image}
            alt={product.name}
            className="w-full h-full object-cover group-hover:scale-105 transition duration-300"
          />
          {!product.inStock && (
            <div className="absolute inset-0 bg-black/60 flex items-center justify-center">
              <span className="text-white font-bold">غير متوفر</span>
            </div>
          )}
        </div>
      </Link>

      <div className="p-4">
        <span className="inline-block bg-blue-100 text-blue-700 text-xs px-2 py-1 rounded-full mb-2">
          {product.category}
        </span>
        <Link to={`/product/${product.id}`}>
          <h3 className="font-semibold text-gray-900 mb-1 hover:text-blue-600 transition">
            {product.name}
          </h3>
        </Link>
        <div className="flex items-center gap-1 mb-3 text-sm text-yellow-500">
          ⭐ {product.rating}
        </div>
        <div className="flex items-center justify-between">
          <span className="text-lg font-bold text-blue-600">{product.price} $</span>
          <button
            onClick={() => addItem(product)}
            disabled={!product.inStock}
            className="bg-blue-600 text-white px-3 py-1 rounded-lg text-sm hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed"
          >
            أضف 🛒
          </button>
        </div>
      </div>
    </div>
  );
}

الخطوة 8: البحث والتصفية

src/components/SearchBar.tsx:

interface SearchBarProps {
  value: string;
  onChange: (value: string) => void;
}

export function SearchBar({ value, onChange }: SearchBarProps) {
  return (
    <div className="relative">
      <input
        type="text"
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder="ابحث عن منتج..."
        className="w-full px-4 py-3 pr-10 rounded-xl border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500"
      />
      <span className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">
        🔍
      </span>
    </div>
  );
}

src/components/Filters.tsx:

import { Category } from "../types";

interface FiltersProps {
  category: Category;
  onCategoryChange: (cat: Category) => void;
  sortBy: "default" | "price-asc" | "price-desc";
  onSortChange: (sort: "default" | "price-asc" | "price-desc") => void;
}

const categories: Category[] = ["الكل", "إلكترونيات", "ملابس", "كتب", "منزل"];

export function Filters({ category, onCategoryChange, sortBy, onSortChange }: FiltersProps) {
  return (
    <div className="flex flex-wrap gap-4 mb-6">
      <div className="flex flex-wrap gap-2">
        {categories.map((cat) => (
          <button
            key={cat}
            onClick={() => onCategoryChange(cat)}
            className={`px-4 py-2 rounded-lg transition ${
              category === cat
                ? "bg-blue-600 text-white"
                : "bg-white text-gray-700 border border-gray-200 hover:border-blue-300"
            }`}
          >
            {cat}
          </button>
        ))}
      </div>

      <select
        value={sortBy}
        onChange={(e) => onSortChange(e.target.value as any)}
        className="px-4 py-2 rounded-lg border border-gray-200"
      >
        <option value="default">ترتيب افتراضي</option>
        <option value="price-asc">السعر: من الأقل</option>
        <option value="price-desc">السعر: من الأعلى</option>
      </select>
    </div>
  );
}

الخطوة 9: الصفحة الرئيسية

src/pages/Home.tsx:

import { useMemo, useState } from "react";
import { ProductCard } from "../components/ProductCard";
import { SearchBar } from "../components/SearchBar";
import { Filters } from "../components/Filters";
import { products } from "../data/products";
import { Category } from "../types";

export function Home() {
  const [search, setSearch] = useState("");
  const [category, setCategory] = useState<Category>("الكل");
  const [sortBy, setSortBy] = useState<"default" | "price-asc" | "price-desc">("default");

  const filtered = useMemo(() => {
    let result = [...products];

    // البحث
    if (search) {
      result = result.filter((p) =>
        p.name.toLowerCase().includes(search.toLowerCase())
      );
    }

    // التصفية
    if (category !== "الكل") {
      result = result.filter((p) => p.category === category);
    }

    // الترتيب
    if (sortBy === "price-asc") {
      result.sort((a, b) => a.price - b.price);
    } else if (sortBy === "price-desc") {
      result.sort((a, b) => b.price - a.price);
    }

    return result;
  }, [search, category, sortBy]);

  return (
    <main className="max-w-7xl mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold text-gray-900 mb-6">
        🛍️ تصفح المنتجات
      </h1>

      <div className="mb-6">
        <SearchBar value={search} onChange={setSearch} />
      </div>

      <Filters
        category={category}
        onCategoryChange={setCategory}
        sortBy={sortBy}
        onSortChange={setSortBy}
      />

      {filtered.length === 0 ? (
        <div className="text-center py-16">
          <span className="text-6xl mb-4 inline-block">🔍</span>
          <p className="text-gray-500">لا توجد منتجات مطابقة</p>
        </div>
      ) : (
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
          {filtered.map((product) => (
            <ProductCard key={product.id} product={product} />
          ))}
        </div>
      )}
    </main>
  );
}

الخطوة 10: صفحة تفاصيل المنتج

src/pages/ProductDetail.tsx:

import { useParams, Link } from "react-router-dom";
import { products } from "../data/products";
import { useCart } from "../contexts/CartContext";

export function ProductDetail() {
  const { id } = useParams<{ id: string }>();
  const product = products.find((p) => p.id === Number(id));
  const { addItem } = useCart();

  if (!product) {
    return (
      <div className="max-w-4xl mx-auto px-4 py-16 text-center">
        <h1 className="text-3xl font-bold text-gray-900 mb-4">المنتج غير موجود</h1>
        <Link to="/" className="text-blue-600 hover:underline">
          العودة للمنتجات
        </Link>
      </div>
    );
  }

  return (
    <main className="max-w-6xl mx-auto px-4 py-8">
      <Link to="/" className="text-blue-600 hover:underline mb-6 inline-block">
        ← العودة للمنتجات
      </Link>

      <div className="grid md:grid-cols-2 gap-8 bg-white rounded-2xl p-8 shadow-sm">
        <div>
          <img
            src={product.image}
            alt={product.name}
            className="w-full h-96 object-cover rounded-xl"
          />
        </div>

        <div>
          <span className="inline-block bg-blue-100 text-blue-700 text-sm px-3 py-1 rounded-full mb-4">
            {product.category}
          </span>
          <h1 className="text-3xl font-bold text-gray-900 mb-4">
            {product.name}
          </h1>
          <div className="flex items-center gap-2 mb-4 text-yellow-500">
            ⭐ {product.rating} / 5
          </div>
          <p className="text-gray-600 leading-relaxed mb-6">
            {product.description}
          </p>
          <p className="text-4xl font-bold text-blue-600 mb-6">
            {product.price} $
          </p>

          <button
            onClick={() => addItem(product)}
            disabled={!product.inStock}
            className="w-full bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700 transition disabled:opacity-50"
          >
            {product.inStock ? "🛒 أضف للسلة" : "غير متوفر"}
          </button>
        </div>
      </div>
    </main>
  );
}

الخطوة 11: شريط جانبي للسلة

src/components/CartSidebar.tsx:

import { useCart } from "../contexts/CartContext";

interface CartSidebarProps {
  isOpen: boolean;
  onClose: () => void;
}

export function CartSidebar({ isOpen, onClose }: CartSidebarProps) {
  const { items, updateQuantity, removeItem, total, clearCart } = useCart();

  const TAX_RATE = 0.15;
  const tax = total * TAX_RATE;
  const finalTotal = total + tax;

  if (!isOpen) return null;

  return (
    <>
      {/* Overlay */}
      <div
        className="fixed inset-0 bg-black/60 z-50"
        onClick={onClose}
      />

      {/* Sidebar */}
      <div className="fixed top-0 left-0 h-full w-full max-w-md bg-white z-50 shadow-2xl flex flex-col">
        <div className="p-4 border-b flex items-center justify-between">
          <h2 className="text-xl font-bold">🛍️ سلة التسوق</h2>
          <button
            onClick={onClose}
            className="text-gray-500 hover:text-gray-700 text-2xl"
          >
            ✕
          </button>
        </div>

        <div className="flex-1 overflow-y-auto p-4">
          {items.length === 0 ? (
            <div className="text-center py-16">
              <span className="text-6xl mb-4 inline-block">🛒</span>
              <p className="text-gray-500">السلة فارغة</p>
            </div>
          ) : (
            <ul className="space-y-4">
              {items.map((item) => (
                <li key={item.id} className="flex gap-4 bg-gray-50 p-3 rounded-xl">
                  <img
                    src={item.image}
                    alt={item.name}
                    className="w-20 h-20 object-cover rounded-lg"
                  />
                  <div className="flex-1">
                    <h3 className="font-semibold text-sm mb-1">{item.name}</h3>
                    <p className="text-blue-600 font-bold text-sm">
                      {item.price} $
                    </p>
                    <div className="flex items-center gap-2 mt-2">
                      <button
                        onClick={() => updateQuantity(item.id, item.quantity - 1)}
                        className="w-7 h-7 bg-gray-200 rounded-full"
                      >
                        -
                      </button>
                      <span className="font-bold">{item.quantity}</span>
                      <button
                        onClick={() => updateQuantity(item.id, item.quantity + 1)}
                        className="w-7 h-7 bg-gray-200 rounded-full"
                      >
                        +
                      </button>
                      <button
                        onClick={() => removeItem(item.id)}
                        className="text-red-500 mr-auto"
                      >
                        🗑️
                      </button>
                    </div>
                  </div>
                </li>
              ))}
            </ul>
          )}
        </div>

        {items.length > 0 && (
          <div className="p-4 border-t">
            <div className="space-y-2 mb-4 text-sm">
              <div className="flex justify-between">
                <span>المجموع:</span>
                <span>{total.toFixed(2)} $</span>
              </div>
              <div className="flex justify-between text-gray-500">
                <span>الضريبة (15%):</span>
                <span>{tax.toFixed(2)} $</span>
              </div>
              <div className="flex justify-between text-lg font-bold border-t pt-2">
                <span>الإجمالي:</span>
                <span className="text-blue-600">{finalTotal.toFixed(2)} $</span>
              </div>
            </div>
            <button
              onClick={() => {
                alert("تم الطلب بنجاح!");
                clearCart();
                onClose();
              }}
              className="w-full bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700 transition mb-2"
            >
              ✅ إتمام الطلب
            </button>
            <button
              onClick={clearCart}
              className="w-full text-red-600 py-2 text-sm hover:bg-red-50 rounded-lg transition"
            >
              إفراغ السلة
            </button>
          </div>
        )}
      </div>
    </>
  );
}

الخطوة 12: App.tsx

src/App.tsx:

import { useState } from "react";
import { Routes, Route } from "react-router-dom";
import { Header } from "./components/Header";
import { CartSidebar } from "./components/CartSidebar";
import { Home } from "./pages/Home";
import { ProductDetail } from "./pages/ProductDetail";

function App() {
  const [isCartOpen, setIsCartOpen] = useState(false);

  return (
    <div className="min-h-screen">
      <Header onToggleCart={() => setIsCartOpen(true)} />

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/product/:id" element={<ProductDetail />} />
      </Routes>

      <CartSidebar isOpen={isCartOpen} onClose={() => setIsCartOpen(false)} />

      <footer className="bg-gray-900 text-white text-center py-6 mt-16">
        <p>© 2026 متجري — مشروع React متكامل</p>
      </footer>
    </div>
  );
}

export default App;

الخطوة 13: main.tsx

src/main.tsx:

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { CartProvider } from "./contexts/CartContext";
import App from "./App";
import "./index.css";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <BrowserRouter>
      <CartProvider>
        <App />
      </CartProvider>
    </BrowserRouter>
  </StrictMode>
);

🎉 تشغيل المشروع

npm run dev

افتح: http://localhost:5173

🎉 مبروك! لديك متجر إلكتروني كامل!

ما تعلمته في هذا المشروع

في هذا المشروع، طبقت:

  • ✅ React Router — التنقل بين الصفحات.
  • ✅ Context API — إدارة السلة عالمياً.
  • ✅ useState — للحالة المحلية.
  • ✅ useEffect — لحفظ السلة في localStorage.
  • ✅ useMemo — لتحسين أداء التصفية.
  • ✅ TypeScript — للأنواع الآمنة.
  • ✅ Tailwind CSS — لتصميم جميل.
  • ✅ Props — لتمرير البيانات.
  • ✅ القوائم — لعرض المنتجات.
  • ✅ الشروط — لعرض "غير متوفر".

تمارين إضافية

تمرين 1: إضافة صفحة "من نحن"

أضف صفحة /about بتصميم بسيط.

تمرين 2: تصفية حسب السعر

أضف نطاق سعر (min - max) للتصفية.

تمرين 3: تقييم المنتج

أضف إمكانية تقييم المنتجات (نجوم).

تمرين 4: المفضلة

أضف زر "المفضلة" لكل منتج.

تمرين 5: Dark Mode

أضف وضعاً داكناً مع Context.

حل المشاكل الشائعة

🔴 المشكلة 1: useCart must be used within CartProvider

الحل: تأكد من تغليف App بـ <CartProvider>.

🔴 المشكلة 2: الصفحة تُحدّث عند التنقل

الحل: تأكد من استخدام <Link> وليس <a>.

🔴 المشكلة 3: 404 في الإنتاج

الحل: أضف rewrites في firebase.json:

"rewrites": [{ "source": "**", "destination": "/index.html" }]

🔴 المشكلة 4: السلة لا تُحفظ

السبب: localStorage لا يعمل في SSR.

الحل: استخدم useState مع lazy initialization.

🎉 مبروك! أكملت مسار React!

لقد وصلت إلى نهاية المسار. أنت الآن تعرف:

  • ✅ أساسيات React: JSX، المكونات، Props.
  • ✅ إدارة الحالة: useState، useEffect.
  • ✅ الأحداث والنماذج.
  • ✅ القوائم والشروط.
  • ✅ React Router.
  • ✅ Context API.
  • ✅ مشروع متكامل.

ماذا بعد React؟

الآن أنت جاهز لـ:

  1. Next.js — إطار عمل كامل (SSR, SSG).
  2. Redux Toolkit — إدارة حالة متقدمة.
  3. React Query — جلب البيانات.
  4. TypeScript متقدم.
  5. بناء معرض أعمالك — ارفع مشاريعك على GitHub.

الخلاصة

في هذا المشروع، بنيت:

  • ✅ متجر إلكتروني كامل.
  • ✅ بحث وتصفية.
  • ✅ سلة تسوق مع حفظ تلقائي.
  • ✅ صفحة تفاصيل المنتج.
  • ✅ حساب الضريبة والإجمالي.

هذا المشروع هو أساس كل تطبيقات التجارة الإلكترونية. احتفظ بالكود، وطور فيه بنفسك!

🎉 هل أكملت مسار React بالكامل؟ شاركنا في التعليقات!

📚 مقالات ذات صلة

React

Context API في React — إدارة الحالة العامة 2026

دليل عملي مفصل لـ Context API في React — إنشاء Context، Provider، useContext، مع تمارين وحلول

React

React Router — التنقل بين الصفحات في React 2026

دليل عملي مفصل لـ React Router — التنقل، المسارات، الروابط، والمعاملات، مع تمارين وحلول

React

القوائم والشروط في React — دليل شامل 2026

دليل عملي مفصل للقوائم والشروط في React — map, filter, key، والتصيير الشرطي، مع تمارين وحلول