مقدمه
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 استفاده کنید. این کار باعث کاهش باگها و بهبود کیفیت کد میشود.
مقالات مرتبط
برای یادگیری بیشتر، مقالات زیر را مطالعه کنید:
- بهینهسازی SEO برای سایتهای React
- برنامه نویسی بکاند با Laravel و PHP
- ایمنی در برنامه نویسی وب و موبایل
نتیجهگیری
React.js ابزار قدرتمندی برای ساخت سایتهای مدرن است. با یادگیری اصول اولیه و تمرین مداوم، میتوانید سایتهای حرفهای و تعاملی بسازید. علی سلمانیان آماده کمک به شما در پروژههای React است.