راهنمای کامل طراحی سایت با React.js | علی سلمانیان - برنامه نویس وب

راهنمای کامل طراحی سایت با React.js

آموزش جامع و کاربردی برای ساخت سایت‌های مدرن و تعاملی

مقدمه

React.js یکی از محبوب‌ترین کتابخانه‌های JavaScript برای ساخت رابط‌های کاربری تعاملی است. در این مقاله، شما را با اصول اولیه React و نحوه استفاده از آن برای طراحی سایت‌های مدرن آشنا می‌کنم.

چرا React.js؟

React.js مزایای زیادی برای توسعه‌دهندگان وب دارد:

  • Virtual DOM: عملکرد بهتر و سریع‌تر
  • Component-Based: قابلیت استفاده مجدد کد
  • JSX: ترکیب HTML و JavaScript
  • Ecosystem: جامعه بزرگ و ابزارهای متنوع

💡 نکته مهم

React فقط یک کتابخانه است، نه یک فریمورک کامل. برای پروژه‌های بزرگ، نیاز به ابزارهای اضافی مثل Redux یا Context API دارید.

نصب و راه‌اندازی React

برای شروع کار با React، ابتدا Node.js را نصب کنید:

# نصب Create React App
npx create-react-app my-website
cd my-website
npm start

مفاهیم اصلی React

1. Components - قلب React

Component ها بلوک‌های سازنده React هستند. در اینجا یک مثال کامل از یک Component کاربردی می‌بینید:

import React, { useState, useEffect } from 'react';

// Functional Component با Hooks
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('کاربر یافت نشد');
        const userData = await response.json();
        setUser(userData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, [userId]);

  if (loading) return <div>در حال بارگذاری...</div>;
  if (error) return <div>خطا: {error}</div>;
  if (!user) return <div>کاربر یافت نشد</div>;

  return (
    <div className="user-profile">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <p>عضویت از: {new Date(user.createdAt).toLocaleDateString('fa-IR')}</p>
    </div>
  );
}

export default UserProfile;

💡 نکات مهم Component:

  • همیشه از Functional Components استفاده کنید
  • Props را destructure کنید برای خوانایی بهتر
  • Loading و Error states را مدیریت کنید
  • useEffect را برای side effects استفاده کنید

2. State Management پیشرفته

مدیریت state در React نیاز به درک عمیق دارد. در اینجا مثال‌های کاربردی مختلف را می‌بینید:

الف) State ساده با useState:

import React, { useState } from 'react';

function ShoppingCart() {
  const [items, setItems] = useState([]);
  const [total, setTotal] = useState(0);

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

  const removeItem = (productId) => {
    setItems(prevItems => prevItems.filter(item => item.id !== productId));
  };

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

  // محاسبه مجموع
  React.useEffect(() => {
    const newTotal = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
    setTotal(newTotal);
  }, [items]);

  return (
    <div className="shopping-cart">
      <h2>سبد خرید</h2>
      {items.map(item => (
        <div key={item.id} className="cart-item">
          <span>{item.name}</span>
          <input
            type="number"
            value={item.quantity}
            onChange={(e) => updateQuantity(item.id, parseInt(e.target.value))}
            min="1"
          />
          <span>{(item.price * item.quantity).toLocaleString('fa-IR')} تومان</span>
          <button onClick={() => removeItem(item.id)}>حذف</button>
        </div>
      ))}
      <div className="total">
        مجموع: {total.toLocaleString('fa-IR')} تومان
      </div>
    </div>
  );
}

ب) Context API برای State Global:

// AuthContext.js
import React, { createContext, useContext, useReducer } from 'react';

const AuthContext = createContext();

const authReducer = (state, action) => {
  switch (action.type) {
    case 'LOGIN_SUCCESS':
      return {
        ...state,
        user: action.payload,
        isAuthenticated: true,
        loading: false
      };
    case 'LOGIN_FAILURE':
      return {
        ...state,
        user: null,
        isAuthenticated: false,
        loading: false,
        error: action.payload
      };
    case 'LOGOUT':
      return {
        ...state,
        user: null,
        isAuthenticated: false,
        loading: false,
        error: null
      };
    case 'SET_LOADING':
      return { ...state, loading: action.payload };
    default:
      return state;
  }
};

export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, {
    user: null,
    isAuthenticated: false,
    loading: false,
    error: null
  });

  const login = async (credentials) => {
    dispatch({ type: 'SET_LOADING', payload: true });
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      });
      
      if (response.ok) {
        const userData = await response.json();
        dispatch({ type: 'LOGIN_SUCCESS', payload: userData });
      } else {
        const error = await response.json();
        dispatch({ type: 'LOGIN_FAILURE', payload: error.message });
      }
    } catch (error) {
      dispatch({ type: 'LOGIN_FAILURE', payload: 'خطا در اتصال به سرور' });
    }
  };

  const logout = () => {
    dispatch({ type: 'LOGOUT' });
  };

  return (
    <AuthContext.Provider value={Object.assign({}, state, { login, logout })}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth باید داخل AuthProvider استفاده شود');
  }
  return context;
};

3. Hooks پیشرفته و Custom Hooks

Hooks قدرت React را به functional components می‌دهد. در اینجا مثال‌های کاربردی و Custom Hooks می‌بینید:

الف) Custom Hook برای API Calls:

// useApi.js - Custom Hook
import { useState, useEffect, useCallback } from 'react';

export const useApi = (url, options = {}) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchData = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      
      const response = await fetch(url, {
        headers: {
          'Content-Type': 'application/json',
          ...options.headers
        },
        ...options
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [url, options]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  const refetch = useCallback(() => {
    fetchData();
  }, [fetchData]);

  return { data, loading, error, refetch };
};

// استفاده از Custom Hook
function ProductList() {
  const { data: products, loading, error, refetch } = useApi('/api/products');
  
  if (loading) return <div>در حال بارگذاری محصولات...</div>;
  if (error) return <div>خطا: {error}</div>;
  
  return (
    <div>
      <button onClick={refetch}>به‌روزرسانی</button>
      {products?.map(product => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <p>{product.price} تومان</p>
        </div>
      ))}
    </div>
  );
}

ب) Custom Hook برای Local Storage:

// useLocalStorage.js
import { useState, useEffect } from 'react';

export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(`خطا در خواندن localStorage key "${key}":`, error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(`خطا در ذخیره localStorage key "${key}":`, error);
    }
  };

  return [storedValue, setValue];
};

// استفاده در Component
function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
  }, [theme]);

  return (
    <button onClick={toggleTheme}>
      {theme === 'light' ? '🌙' : '☀️'} {theme === 'light' ? 'تاریک' : 'روشن'}
    </button>
  );
}

ج) useReducer برای State پیچیده:

// TodoApp با useReducer
import React, { useReducer, useState } from 'react';

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, {
          id: Date.now(),
          text: action.payload,
          completed: false,
          createdAt: new Date()
        }]
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
    case 'SET_FILTER':
      return { ...state, filter: action.payload };
    case 'CLEAR_COMPLETED':
      return {
        ...state,
        todos: state.todos.filter(todo => !todo.completed)
      };
    default:
      return state;
  }
};

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, {
    todos: [],
    filter: 'all' // all, active, completed
  });
  const [inputValue, setInputValue] = useState('');

  const filteredTodos = state.todos.filter(todo => {
    if (state.filter === 'active') return !todo.completed;
    if (state.filter === 'completed') return todo.completed;
    return true;
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    if (inputValue.trim()) {
      dispatch({ type: 'ADD_TODO', payload: inputValue.trim() });
      setInputValue('');
    }
  };

  return (
    <div className="todo-app">
      <h1>لیست کارها</h1>
      
      <form onSubmit={handleSubmit}>
        <input
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="کار جدید اضافه کنید..."
        />
        <button type="submit">اضافه کردن</button>
      </form>

      <div className="filters">
        {['all', 'active', 'completed'].map(filter => (
          <button
            key={filter}
            className={state.filter === filter ? 'active' : ''}
            onClick={() => dispatch({ type: 'SET_FILTER', payload: filter })}
          >
            {filter === 'all' ? 'همه' : filter === 'active' ? 'فعال' : 'تمام شده'}
          </button>
        ))}
      </div>

      <ul>
        {filteredTodos.map(todo => (
          <li key={todo.id} className={todo.completed ? 'completed' : ''}>
            <span onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}>
              {todo.text}
            </span>
            <button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
              حذف
            </button>
          </li>
        ))}
      </ul>

      {state.todos.some(todo => todo.completed) && (
        <button onClick={() => dispatch({ type: 'CLEAR_COMPLETED' })}>
          پاک کردن کارهای تمام شده
        </button>
      )}
    </div>
  );
}

بهترین روش‌های توسعه

1. ساختار پروژه حرفه‌ای

ساختار منظم پروژه برای نگهداری و توسعه آسان‌تر ضروری است. در اینجا یک ساختار کامل و حرفه‌ای می‌بینید:

src/
├── components/                    # Component های قابل استفاده مجدد
│   ├── common/                   # Component های عمومی
│   │   ├── Button/
│   │   │   ├── Button.jsx
│   │   │   ├── Button.module.css
│   │   │   ├── Button.test.js
│   │   │   └── index.js
│   │   ├── Modal/
│   │   ├── Input/
│   │   └── LoadingSpinner/
│   ├── layout/                   # Component های Layout
│   │   ├── Header/
│   │   ├── Sidebar/
│   │   └── Footer/
│   └── features/                 # Component های مخصوص Feature
│       ├── auth/
│       │   ├── LoginForm/
│       │   └── RegisterForm/
│       └── products/
│           ├── ProductCard/
│           └── ProductList/
├── pages/                        # صفحات اصلی
│   ├── Home/
│   ├── Products/
│   ├── About/
│   └── Contact/
├── hooks/                        # Custom Hooks
│   ├── useApi.js
│   ├── useLocalStorage.js
│   └── useAuth.js
├── services/                     # API calls و Business Logic
│   ├── api/
│   │   ├── auth.js
│   │   ├── products.js
│   │   └── index.js
│   └── storage/
│       ├── localStorage.js
│       └── sessionStorage.js
├── context/                      # Context Providers
│   ├── AuthContext.js
│   ├── ThemeContext.js
│   └── CartContext.js
├── utils/                        # توابع کمکی
│   ├── constants.js
│   ├── helpers.js
│   ├── validators.js
│   └── formatters.js
├── styles/                       # استایل‌های عمومی
│   ├── globals.css
│   ├── variables.css
│   └── components.css
├── assets/                       # فایل‌های استاتیک
│   ├── images/
│   ├── icons/
│   └── fonts/
├── tests/                        # تست‌ها
│   ├── components/
│   ├── hooks/
│   └── utils/
└── App.jsx

📁 نکات مهم ساختار پروژه:

  • Separation of Concerns: هر فایل مسئولیت مشخصی دارد
  • Reusability: Component ها قابل استفاده مجدد هستند
  • Scalability: ساختار برای پروژه‌های بزرگ مناسب است
  • Maintainability: نگهداری و توسعه آسان است

2. Performance Optimization پیشرفته

بهینه‌سازی عملکرد React نیاز به تکنیک‌های پیشرفته دارد:

الف) React.memo و useMemo:

import React, { memo, useMemo, useCallback } from 'react';

// Component بهینه شده با React.memo
const ExpensiveComponent = memo(({ data, onUpdate }) => {
  // محاسبات پیچیده فقط در صورت تغییر data
  const processedData = useMemo(() => {
    console.log('محاسبه مجدد داده‌ها...');
    return data.map(item => ({
      ...item,
      processed: true,
      calculatedValue: item.value * 2
    }));
  }, [data]);

  // Function فقط در صورت تغییر وابستگی‌ها
  const handleClick = useCallback((id) => {
    onUpdate(id);
  }, [onUpdate]);

  return (
    <div>
      {processedData.map(item => (
        <div key={item.id} onClick={() => handleClick(item.id)}>
          {item.name} - {item.calculatedValue}
        </div>
      ))}
    </div>
  );
});

ب) Virtual Scrolling برای لیست‌های بزرگ:

import React, {{ useState, useRef }} from 'react';

const VirtualList = ({{ items, itemHeight = 50, containerHeight = 400 }}) => {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef(null);

  const visibleStart = Math.floor(scrollTop / itemHeight);
  const visibleEnd = Math.min(
    visibleStart + Math.ceil(containerHeight / itemHeight) + 1,
    items.length
  );

  const visibleItems = items.slice(visibleStart, visibleEnd);
  const totalHeight = items.length * itemHeight;
  const offsetY = visibleStart * itemHeight;

  return (
    <div
      ref={containerRef}
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={(e) => setScrollTop(e.target.scrollTop)}
    >
      <div style={{ height: totalHeight, position: 'relative' }}>
        <div
          style={{
            transform: `translateY(${offsetY}px)`,
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0
          }}
        >
          {visibleItems.map((item, index) => (
            <div
              key={visibleStart + index}
              style={{
                height: itemHeight,
                display: 'flex',
                alignItems: 'center',
                padding: '0 16px',
                borderBottom: '1px solid #eee'
              }}
            >
              {item.name}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

3. Error Handling و Error Boundaries

مدیریت خطاها در React نیاز به Error Boundaries دارد:

import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error: error,
      errorInfo: errorInfo
    });
    
    // ارسال خطا به سرویس monitoring
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>مشکلی پیش آمده!</h2>
          <p>لطفاً صفحه را رفرش کنید یا بعداً تلاش کنید.</p>
          {process.env.NODE_ENV === 'development' && (
            <details>
              <summary>جزئیات خطا</summary>
              <pre>{this.state.error && this.state.error.toString()}</pre>
              <pre>{this.state.errorInfo.componentStack}</pre>
            </details>
          )}
        </div>
      );
    }

    return this.props.children;
  }
}

4. Testing با Jest و React Testing Library

تست کردن Component ها برای کیفیت کد ضروری است:

// Button.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Button from './Button';

describe('Button Component', () => {
  test('renders button with text', () => {
    render(<Button>کلیک کنید</Button>);
    expect(screen.getByText('کلیک کنید')).toBeInTheDocument();
  });

  test('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>کلیک کنید</Button>);
    
    fireEvent.click(screen.getByText('کلیک کنید'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  test('applies correct variant class', () => {
    render(<Button variant="primary">دکمه اصلی</Button>);
    expect(screen.getByRole('button')).toHaveClass('btn-primary');
  });

  test('is disabled when disabled prop is true', () => {
    render(<Button disabled>دکمه غیرفعال</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});

// Custom Hook Test
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

test('useCounter hook', () => {
  const { result } = renderHook(() => useCounter(0));

  expect(result.current.count).toBe(0);

  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(1);

  act(() => {
    result.current.decrement();
  });

  expect(result.current.count).toBe(0);
});

⚠️ نکات مهم Testing:

  • همیشه Component ها را تست کنید
  • Custom Hook ها را جداگانه تست کنید
  • Integration Test ها را فراموش نکنید
  • Coverage بالای 80% داشته باشید

SEO و React

برای بهبود SEO سایت‌های React:

  • استفاده از Next.js برای SSR
  • اضافه کردن meta tags مناسب
  • استفاده از React Helmet
  • بهینه‌سازی تصاویر و لینک‌ها

🚀 پیشنهاد حرفه‌ای

برای پروژه‌های تجاری، حتماً از TypeScript استفاده کنید. این کار باعث کاهش باگ‌ها و بهبود کیفیت کد می‌شود.

مقالات مرتبط

برای یادگیری بیشتر، مقالات زیر را مطالعه کنید:

نتیجه‌گیری

React.js ابزار قدرتمندی برای ساخت سایت‌های مدرن است. با یادگیری اصول اولیه و تمرین مداوم، می‌توانید سایت‌های حرفه‌ای و تعاملی بسازید. علی سلمانیان آماده کمک به شما در پروژه‌های React است.

درباره نویسنده

علی سلمانیان - برنامه نویس وب و اندروید با بیش از 5 سال تجربه در توسعه نرم‌افزار. متخصص React، Laravel، Java و Kotlin.

📧 alisalmanian1395@gmail.com | 📱 +98 938 822 2808