أساسيات React.js

التنسيق في React

18 دقيقة الدرس 18 من 40

مقدمة إلى التنسيق في React

يوفر React عدة طرق لتنسيق المكونات، كل منها له مزايا فريدة. يستكشف هذا الدرس الأنماط المضمنة ووحدات CSS وstyled-components وTailwind CSS ومكتبات CSS-in-JS، مما يساعدك على اختيار النهج المناسب لمشاريعك.

CSS التقليدي مع className

النهج الأبسط يستخدم ملفات CSS العادية مع خاصية className:

// src/components/Button.jsx
import './Button.css';

function Button({ children, variant = 'primary' }) {
  return (
    <button className={`btn btn-${variant}`}>
      {children}
    </button>
  );
}

export default Button;
/* src/components/Button.css */
.btn {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 0.375rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
}

.btn-primary {
  background-color: #3b82f6;
  color: white;
}

.btn-primary:hover {
  background-color: #2563eb;
}

.btn-secondary {
  background-color: #6b7280;
  color: white;
}

.btn-secondary:hover {
  background-color: #4b5563;
}

تحذير: CSS التقليدي له نطاق عام. يمكن أن تتعارض أسماء الفئات عبر المكونات. استخدم اصطلاحات التسمية مثل BEM أو وحدات CSS لتجنب التعارضات.

وحدات CSS: التنسيق المحدد النطاق

وحدات CSS تحدد نطاق الأنماط تلقائيًا للمكونات، مما يمنع تعارضات التسمية:

// src/components/Card.jsx
import styles from './Card.module.css';

function Card({ title, content, featured }) {
  return (
    <div className={`${styles.card} ${featured ? styles.featured : ''}`}>
      <h3 className={styles.title}>{title}</h3>
      <p className={styles.content}>{content}</p>
    </div>
  );
}

// بديل: استخدام دمج المصفوفة
function Card2({ title, content, featured }) {
  const cardClasses = [
    styles.card,
    featured && styles.featured
  ].filter(Boolean).join(' ');

  return (
    <div className={cardClasses}>
      <h3 className={styles.title}>{title}</h3>
      <p className={styles.content}>{content}</p>
    </div>
  );
}

export default Card;
/* src/components/Card.module.css */
.card {
  background: white;
  border-radius: 0.5rem;
  padding: 1.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s, box-shadow 0.2s;
}

.card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.featured {
  border: 2px solid #3b82f6;
  background: linear-gradient(to bottom right, #eff6ff, white);
}

.title {
  font-size: 1.25rem;
  font-weight: 700;
  color: #1f2937;
  margin-bottom: 0.5rem;
}

.content {
  color: #6b7280;
  line-height: 1.6;
}

نصيحة: وحدات CSS تعمل مع Create React App و Vite مباشرةً. فقط قم بتسمية ملفاتك *.module.css.

الأنماط المضمنة: كائنات JavaScript

يدعم React الأنماط المضمنة باستخدام كائنات JavaScript مع خصائص camelCase:

function InlineStylesExample() {
  const containerStyle = {
    backgroundColor: '#f3f4f6',
    padding: '2rem',
    borderRadius: '0.5rem',
    maxWidth: '600px',
    margin: '0 auto'
  };

  const headingStyle = {
    fontSize: '2rem',
    fontWeight: 'bold',
    color: '#1f2937',
    marginBottom: '1rem'
  };

  const textStyle = {
    color: '#4b5563',
    lineHeight: 1.6
  };

  return (
    <div style={containerStyle}>
      <h2 style={headingStyle}>الأنماط المضمنة</h2>
      <p style={textStyle}>يستخدم هذا المكون الأنماط المضمنة.</p>
    </div>
  );
}

// الأنماط المضمنة الديناميكية بناءً على الخصائص
function Alert({ type, message }) {
  const baseStyle = {
    padding: '1rem',
    borderRadius: '0.375rem',
    marginBottom: '1rem',
    border: '1px solid'
  };

  const typeStyles = {
    success: {
      backgroundColor: '#d1fae5',
      borderColor: '#10b981',
      color: '#065f46'
    },
    error: {
      backgroundColor: '#fee2e2',
      borderColor: '#ef4444',
      color: '#991b1b'
    },
    warning: {
      backgroundColor: '#fef3c7',
      borderColor: '#f59e0b',
      color: '#92400e'
    }
  };

  const alertStyle = { ...baseStyle, ...typeStyles[type] };

  return <div style={alertStyle}>{message}</div>;
}

ملاحظة: الأنماط المضمنة لا تدعم الفئات الزائفة (:hover، :focus) أو استعلامات الوسائط. استخدم CSS/وحدات CSS لهذه الميزات.

styled-components: CSS-in-JS

styled-components هي مكتبة CSS-in-JS شائعة تتيح لك كتابة CSS مباشرةً في JavaScript:

// التثبيت
npm install styled-components

// src/components/StyledButton.jsx
import styled from 'styled-components';

// مكون منسق أساسي
const StyledButton = styled.button`
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 0.375rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
  background-color: #3b82f6;
  color: white;

  &:hover {
    background-color: #2563eb;
    transform: translateY(-1px);
  }

  &:active {
    transform: translateY(0);
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

// مكون منسق بالخصائص
const Button = styled.button`
  padding: ${props => props.size === 'large' ? '1rem 2rem' : '0.5rem 1rem'};
  background-color: ${props => {
    switch(props.variant) {
      case 'primary': return '#3b82f6';
      case 'success': return '#10b981';
      case 'danger': return '#ef4444';
      default: return '#6b7280';
    }
  }};
  color: white;
  border: none;
  border-radius: 0.375rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;

  &:hover {
    opacity: 0.9;
    transform: translateY(-2px);
  }
`;

// توسيع المكونات المنسقة
const IconButton = styled(Button)`
  display: flex;
  align-items: center;
  gap: 0.5rem;

  svg {
    width: 1.25rem;
    height: 1.25rem;
  }
`;

// الاستخدام
function App() {
  return (
    <div>
      <StyledButton>انقر هنا</StyledButton>
      <Button variant="primary" size="large">أساسي</Button>
      <Button variant="success">نجاح</Button>
      <Button variant="danger">خطر</Button>
      <IconButton variant="primary">
        <span>➤</span> مع أيقونة
      </IconButton>
    </div>
  );
}

أفضل ممارسة: أنشئ كائن سمة للألوان والتباعد والطباعة المتسقة عبر styled-components.

Tailwind CSS مع React

Tailwind هو إطار عمل CSS يعتمد على الأدوات ويوفر فئات جاهزة:

// التثبيت لـ Vite
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

// 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;

// src/components/TailwindCard.jsx
function TailwindCard({ title, description, image, featured }) {
  return (
    <div className={`
      rounded-lg shadow-md overflow-hidden transition-all duration-300
      hover:shadow-xl hover:-translate-y-1
      ${featured ? 'ring-2 ring-blue-500 bg-gradient-to-br from-blue-50 to-white' : 'bg-white'}
    `}>
      <img
        src={image}
        alt={title}
        className="w-full h-48 object-cover"
      />
      <div className="p-6">
        <h3 className="text-xl font-bold text-gray-800 mb-2">
          {title}
        </h3>
        <p className="text-gray-600 leading-relaxed">
          {description}
        </p>
        <button className="
          mt-4 px-6 py-2 bg-blue-500 text-white font-semibold rounded-md
          hover:bg-blue-600 active:bg-blue-700 transition-colors
          focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
        ">
          معرفة المزيد
        </button>
      </div>
    </div>
  );
}

// مكون زر قابل لإعادة الاستخدام مع Tailwind
function Button({ children, variant = 'primary', size = 'md', ...props }) {
  const baseClasses = 'font-semibold rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';

  const variantClasses = {
    primary: 'bg-blue-500 hover:bg-blue-600 text-white focus:ring-blue-500',
    secondary: 'bg-gray-500 hover:bg-gray-600 text-white focus:ring-gray-500',
    success: 'bg-green-500 hover:bg-green-600 text-white focus:ring-green-500',
    danger: 'bg-red-500 hover:bg-red-600 text-white focus:ring-red-500',
    outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-50 focus:ring-blue-500'
  };

  const sizeClasses = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2',
    lg: 'px-6 py-3 text-lg'
  };

  const className = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`;

  return (
    <button className={className} {...props}>
      {children}
    </button>
  );
}

التنسيق الشرطي والديناميكي

تقنيات لتطبيق الأنماط بشكل شرطي:

// استخدام القوالب الحرفية
function ConditionalStyling({ isActive, isDisabled, size }) {
  return (
    <button
      className={`
        btn
        ${isActive ? 'btn-active' : 'btn-inactive'}
        ${isDisabled ? 'btn-disabled' : ''}
        ${size === 'large' ? 'btn-lg' : 'btn-md'}
      `}
    >
      انقر هنا
    </button>
  );
}

// استخدام مكتبة classnames (موصى به)
import classNames from 'classnames';

function BetterConditional({ isActive, isDisabled, size }) {
  const buttonClasses = classNames(
    'btn',
    {
      'btn-active': isActive,
      'btn-inactive': !isActive,
      'btn-disabled': isDisabled,
      'btn-lg': size === 'large',
      'btn-md': size === 'medium',
      'btn-sm': size === 'small'
    }
  );

  return <button className={buttonClasses}>انقر هنا</button>;
}

// الأنماط المضمنة الديناميكية
function ProgressBar({ progress, color = 'blue' }) {
  const colorMap = {
    blue: '#3b82f6',
    green: '#10b981',
    red: '#ef4444'
  };

  return (
    <div style={{
      width: '100%',
      height: '1rem',
      backgroundColor: '#e5e7eb',
      borderRadius: '0.25rem',
      overflow: 'hidden'
    }}>
      <div style={{
        width: `${progress}%`,
        height: '100%',
        backgroundColor: colorMap[color],
        transition: 'width 0.3s ease'
      }} />
    </div>
  );
}

متغيرات CSS مع React

استخدم خصائص CSS المخصصة للسمات:

// src/App.jsx
import { useState } from 'react';
import './theme.css';

function App() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <div data-theme={theme}>
      <button onClick={toggleTheme}>تبديل السمة</button>
      <div className="card">
        <h2>بطاقة بسمة</h2>
        <p>تتكيف هذه البطاقة مع السمة.</p>
      </div>
    </div>
  );
}

/* src/theme.css */
:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f3f4f6;
  --text-primary: #1f2937;
  --text-secondary: #6b7280;
  --border-color: #e5e7eb;
  --accent: #3b82f6;
}

[data-theme="dark"] {
  --bg-primary: #1f2937;
  --bg-secondary: #111827;
  --text-primary: #f9fafb;
  --text-secondary: #d1d5db;
  --border-color: #374151;
  --accent: #60a5fa;
}

body {
  background-color: var(--bg-primary);
  color: var(--text-primary);
}

.card {
  background-color: var(--bg-secondary);
  border: 1px solid var(--border-color);
  padding: 1.5rem;
  border-radius: 0.5rem;
}

.card h2 {
  color: var(--accent);
}

تمرين 1: تطبيق قائمة المهام بوحدات CSS

المهمة: أنشئ تطبيق قائمة مهام باستخدام وحدات CSS:

  • أنشئ مكونات TodoItem و TodoList و AddTodo
  • استخدم ملفات *.module.css منفصلة لكل مكون
  • نسّق المهام المكتملة بشكل مختلف (خط مشطوب، شفافية)
  • أضف تأثيرات التمرير على عناصر المهام
  • أنشئ نظام أولوية بألوان مختلفة (عالية/متوسطة/منخفضة)

تمرين 2: بطاقة منتج Tailwind

المهمة: بناء مكون بطاقة منتج باستخدام Tailwind CSS:

  • قم بتضمين صورة المنتج والعنوان والسعر والتقييم وزر "أضف إلى السلة"
  • أضف تصميم متجاوب (يتكدس على الجوال، شبكة على سطح المكتب)
  • نفّذ نوع "مميز" بتنسيق مختلف
  • أضف تأثيرات التمرير والانتقالات
  • أنشئ شارة خصم لعناصر التخفيض

تمرين 3: مبدل السمات

المهمة: بناء مبدل سمات باستخدام متغيرات CSS:

  • أنشئ سمات فاتحة ومظلمة وعالية التباين
  • خزّن السمة المحددة في localStorage
  • طبّق السمة عند التحميل الأولي بناءً على التفضيل المخزن
  • أنشئ قائمة منسدلة للاختيار بين السمات
  • يجب أن تؤثر السمة على جميع المكونات (الأزرار، البطاقات، النص)

الخلاصة

في هذا الدرس، استكشفت طرق التنسيق المختلفة في React:

  • CSS التقليدي مع className لاحتياجات التنسيق البسيطة
  • وحدات CSS للأنماط المحددة النطاق والخاصة بالمكون
  • الأنماط المضمنة للتنسيق الديناميكي المدفوع بـ JavaScript
  • styled-components لـ CSS-in-JS القوي بميزات CSS الكاملة
  • Tailwind CSS للتطوير السريع بفئات الأدوات
  • تقنيات التنسيق الشرطي وإدارة الفئات الديناميكية
  • متغيرات CSS للسمات وأنظمة التصميم

في الدرس التالي، سنتعمق في دورة حياة المكونات وتحسين الأداء باستخدام React.memo و useMemo و useCallback.