React 19: انقلاب در توسعه فرانتاند - راهنمای کامل ۲۰۲۶
📊 آمارهای کلیدی React در ۲۰۲۶:
- 🌟 بیش از ۲۲۰ هزار ستاره در گیتهاب (دومین پروژه محبوب دنیا)
- 🏢 استفاده توسط غولهای تکنولوژی: Facebook, Instagram, Netflix, WhatsApp, Uber
- 📦 بیش از ۲۰ میلیون دانلود ماهانه از npm
- 💼 ۴۵٪ از توسعهدهندگان فرانتاند از React استفاده میکنند
- ⚡ بهبود ۳۰٪ عملکرد در نسخه ۱۹ نسبت به نسخه ۱۸
- 🔧 بیش از ۱۰۰۰ کتابخانه و ابزار در اکوسیستم React
🎯 خلاصه اجرایی:
React.js محبوبترین کتابخانه جاوااسکریپت برای ساخت رابطهای کاربری است که توسط فیسبوک توسعه یافته و نگهداری میشود. React با معرفی معماری کامپوننتمحور و Virtual DOM، انقلابی در توسعه فرانتاند ایجاد کرد. نسخه ۱۹ React با ویژگیهای جدید مانند React Compiler، Actions، و بهبودهای چشمگیر در Server Components، تجربه توسعهدهندگان و عملکرد برنامهها را متحول کرده است. در این مقاله از درخت کد، به بررسی عمیق این کتابخانه قدرتمند میپردازیم.
مقدمه: داستان React - از فیسبوک تا سلطان فرانتاند
در سال ۲۰۱۱، تیم فیسبوک با چالش بزرگی روبرو بود: اعلانها (Notifications) در فیسبوک مدام باگ داشت و کدهای اسپاگتی جیکوئری به سادگی قابل نگهداری نبودند. جردن واک (Jordan Walke) مهندس فیسبوک، یک نمونه اولیه به نام "FaxJS" ساخت که بعدها به React تبدیل شد. ایده اصلی: "اگر هر بار که داده تغییر میکند، کل رابط کاربری را از نو بسازیم، چه میشود؟"
یادم میآید اولین بار در سال ۲۰۱۷ با React آشنا شدم. آن زمان React 16 تازه منتشر شده بود و مفهوم "Hooks" هنوز وجود نداشت. امروز پس از سالها توسعه پروژههای بزرگ با React و تدریس آن در درخت کد، میتوانم بگویم React نه فقط یک کتابخانه، بلکه یک اکوسیستم کامل است.
۱.۱ تاریخچه نسخههای React
۲۰۱۳: React open source
انتشار عمومی React در JSConf US
۲۰۱۵: React Native
توسعه اپلیکیشن موبایل با React
۲۰۱۶: React 15
معرفی معماری جدید و بهبود عملکرد
۲۰۱۷: React 16
Fiber Architecture - بازنویسی هسته React
۲۰۱۹: React 16.8
انقلاب Hooks - پایان Class Components
۲۰۲۰: React 17
بدون ویژگی جدید - آمادهسازی برای نسخه بعدی
۲۰۲۲: React 18
Concurrent Rendering, Suspense, Transitions
۲۰۲۴-۲۰۲۶: React 19
React Compiler, Actions, Server Components پایدار
بخش اول: فلسفه React و مفاهیم پایه
۱.۱ چرا React؟ مقایسه با سایر فریمورکها
| ویژگی | React | Vue | Angular | Svelte |
|---|---|---|---|---|
| نوع | ✅ کتابخانه | ✅ فریمورک | ✅ فریمورک | ✅ کامپایلر |
| محبوبیت (npm downloads) | ⭐ ۲۵M/ماه | ⭐ ۱۰M/ماه | ⭐ ۵M/ماه | ⭐ ۲M/ماه |
| منحنی یادگیری | ⚠️ متوسط | ✅ آسان | ❌ سخت | ✅ آسان |
| بازار کار ایران | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | ⭐️⭐️ | ⭐️⭐️ |
| انعطافپذیری | ✅ بسیار بالا | ✅ بالا | ⚠️ محدود | ✅ بالا |
| اکوسیستم | ⭐ بسیار بزرگ | ⭐ بزرگ | ⭐ متوسط | ⭐ در حال رشد |
۱.۲ مفاهیم کلیدی React
🧩 کامپوننت (Component)
قطعات کوچک و قابل استفاده مجدد که هر کدام مسئول یک بخش از رابط کاربری هستند.
📊 Props
دادههایی که از کامپوننت والد به فرزند ارسال میشوند (خواندنی).
🔄 State
دادههای داخلی کامپوننت که با تغییرشان، کامپوننت دوباره رندر میشود.
🌳 Virtual DOM
یک کپی سبک از DOM واقعی که React برای بهینهسازی رندرینگ استفاده میکند.
⚡ Hooks
توابعی که به شما امکان استفاده از state و قابلیتهای React در کامپوننتهای تابعی میدهند.
🔄 JSX
یک syntax شبیه HTML که به شما امکان میدهد ساختار UI را درون جاوااسکریپت بنویسید.
بخش دوم: شروع کار با React 19
۲.۱ روشهای راهاندازی پروژه React
روش ۱: Create React App (سنتی)
# نصب و ایجاد پروژه
npx create-react-app my-app
cd my-app
npm start
# ساختار پروژه
my-app/
├── public/
├── src/
│ ├── App.js
│ ├── App.css
│ ├── index.js
│ └── index.css
├── package.json
└── README.md
روش ۲: Vite (سریعتر و مدرنتر - توصیه شده)
# ایجاد پروژه با Vite
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev
# ساختار پروژه با Vite
my-app/
├── index.html
├── src/
│ ├── App.jsx
│ ├── App.css
│ ├── main.jsx
│ └── index.css
├── vite.config.js
└── package.json
روش ۳: Next.js (فریمورک React برای production)
# ایجاد پروژه Next.js
npx create-next-app@latest my-app
cd my-app
npm run dev
# ساختار پروژه Next.js
my-app/
├── app/
│ ├── layout.js
│ ├── page.js
│ └── globals.css
├── public/
├── next.config.js
└── package.json
۲.۲ اولین کامپوننت React
کامپوننت ساده (Counter):
// Counter.jsx
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div className="counter-container">
<h2>شمارنده: {count}</h2>
<button onClick={() => setCount(count + 1)}>
افزایش
</button>
<button onClick={() => setCount(count - 1)}>
کاهش
</button>
<button onClick={() => setCount(0)}>
ریست
</button>
</div>
);
}
export default Counter;
// App.jsx
import Counter from './Counter';
function App() {
return (
<div>
<h1>اولین اپلیکیشن React من</h1>
<Counter />
</div>
);
}
export default App;
بخش سوم: Hooks در React 19
۳.۱ Hooks پایه (Essential Hooks)
| Hook | کاربرد | مثال |
|---|---|---|
useState |
مدیریت state داخلی کامپوننت | const [count, setCount] = useState(0) |
useEffect |
مدیریت side effects (API calls, subscriptions) | useEffect(() => { document.title = count }, [count]) |
useContext |
دسترسی به Context سراسری | const theme = useContext(ThemeContext) |
useReducer |
مدیریت state پیچیده با Reducer | const [state, dispatch] = useReducer(reducer, initialState) |
مثال کاربردی useEffect:
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// دریافت لیست کاربران از API
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {
if (!response.ok) {
throw new Error('خطا در دریافت اطلاعات');
}
return response.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []); // آرایه خالی یعنی فقط یک بار اجرا شود
if (loading) return <div>در حال بارگذاری...</div>;
if (error) return <div>خطا: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
);
}
۳.۲ Hooks جدید در React 19
useActionState - مدیریت فرم و اکشنها:
import { useActionState } from 'react';
async function submitForm(prevState, formData) {
const name = formData.get('name');
const email = formData.get('email');
try {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ name, email }),
headers: { 'Content-Type': 'application/json' }
});
if (response.ok) {
return { success: true, message: 'کاربر با موفقیت اضافه شد' };
} else {
return { success: false, message: 'خطا در ثبت اطلاعات' };
}
} catch (error) {
return { success: false, message: error.message };
}
}
function AddUserForm() {
const [state, formAction, isPending] = useActionState(submitForm, { success: false, message: '' });
return (
<form action={formAction}>
<div>
<label>نام:</label>
<input type="text" name="name" required />
</div>
<div>
<label>ایمیل:</label>
<input type="email" name="email" required />
</div>
<button type="submit" disabled={isPending}>
{isPending ? 'در حال ارسال...' : 'افزودن کاربر'}
</button>
{state.message && (
<p className={state.success ? 'text-green-600' : 'text-red-600'}>
{state.message}
</p>
)}
</form>
);
}
useOptimistic - بهروزرسانی خوشبینانه:
import { useOptimistic, useRef } from 'react';
function MessageList({ initialMessages, sendMessage }) {
const formRef = useRef();
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
initialMessages,
(state, newMessage) => [...state, { id: Date.now(), text: newMessage, sending: true }]
);
async function formAction(formData) {
const message = formData.get('message');
addOptimisticMessage(message);
formRef.current.reset();
await sendMessage(message);
}
return (
<>
<ul>
{optimisticMessages.map((msg) => (
<li key={msg.id}>
{msg.text}
{msg.sending && <span> (در حال ارسال...)</span>}
</li>
))}
</ul>
<form ref={formRef} action={formAction}>
<input type="text" name="message" required />
<button type="submit">ارسال</button>
</form>
</>
);
}
use - دسترسی به منابع در کامپوننت:
import { use, Suspense } from 'react';
const fetchUsers = fetch('https://api.example.com/users').then(res => res.json());
function UserProfile({ userId }) {
const user = use(fetchUsers).find(u => u.id === userId);
return (
<div>
<h2>{user.name}</h2>
<p>ایمیل: {user.email}</p>
</div>
);
}
function UserList() {
const users = use(fetchUsers);
return (
<div>
{users.map(user => (
<UserProfile key={user.id} userId={user.id} />
))}
</div>
);
}
function App() {
return (
<Suspense fallback={<div>در حال بارگذاری...</div>}>
<UserList />
</Suspense>
);
}
بخش چهارم: پروژههای عملی با React
۴.۱ نمونه ۱: Todo List با React
اپلیکیشن مدیریت وظایف:
import { useState } from 'react';
import './TodoApp.css';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
const [filter, setFilter] = useState('all'); // all, active, completed
const addTodo = () => {
if (inputValue.trim()) {
setTodos([
...todos,
{ id: Date.now(), text: inputValue, completed: false }
]);
setInputValue('');
}
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
const clearCompleted = () => {
setTodos(todos.filter(todo => !todo.completed));
};
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
const activeCount = todos.filter(todo => !todo.completed).length;
return (
<div className="todo-container">
<h1>مدیریت وظایف</h1>
<div className="input-group">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="وظیفه جدید..."
/>
<button onClick={addTodo}>افزودن</button>
</div>
<div className="filters">
<button onClick={() => setFilter('all')} className={filter === 'all' ? 'active' : ''}>
همه ({todos.length})
</button>
<button onClick={() => setFilter('active')} className={filter === 'active' ? 'active' : ''}>
فعال ({activeCount})
</button>
<button onClick={() => setFilter('completed')} className={filter === 'completed' ? 'active' : ''}>
انجام شده ({todos.length - activeCount})
</button>
</div>
<ul className="todo-list">
{filteredTodos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>حذف</button>
</li>
))}
</ul>
{todos.length > 0 && (
<button className="clear-btn" onClick={clearCompleted}>
حذف موارد انجام شده
</button>
)}
</div>
);
}
export default TodoApp;
۴.۲ نمونه ۲: برنامه هواشناسی با API
دریافت اطلاعات آب و هوا:
import { useState, useEffect } from 'react';
import './WeatherApp.css';
function WeatherApp() {
const [city, setCity] = useState('Tehran');
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const API_KEY = 'YOUR_API_KEY';
const fetchWeather = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=fa`
);
if (!response.ok) {
throw new Error('شهر مورد نظر یافت نشد');
}
const data = await response.json();
setWeather(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchWeather();
}, []);
const handleSubmit = (e) => {
e.preventDefault();
fetchWeather();
};
return (
<div className="weather-container">
<h1>پیشبینی آب و هوا</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={city}
onChange={(e) => setCity(e.target.value)}
placeholder="نام شهر را وارد کنید..."
/>
<button type="submit" disabled={loading}>
{loading ? 'در حال جستجو...' : 'جستجو'}
</button>
</form>
{error && <div className="error">{error}</div>}
{weather && (
<div className="weather-info">
<h2>{weather.name}</h2>
<div className="weather-main">
<img
src={`http://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`}
alt={weather.weather[0].description}
/>
<div className="temperature">
{Math.round(weather.main.temp)}°C
</div>
</div>
<div className="weather-details">
<p>وضعیت: {weather.weather[0].description}</p>
<p>رطوبت: {weather.main.humidity}%</p>
<p>باد: {weather.wind.speed} m/s</p>
</div>
</div>
)}
</div>
);
}
export default WeatherApp;
بخش پنجم: مدیریت State در React
۵.۱ Context API
مدیریت تم با Context:
// ThemeContext.js
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [isDark, setIsDark] = useState(false);
const toggleTheme = () => setIsDark(!isDark);
return (
<ThemeContext.Provider value={{ isDark, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// App.js
function App() {
return (
<ThemeProvider>
<Header />
<MainContent />
<Footer />
</ThemeProvider>
);
}
// Header.js
function Header() {
const { isDark, toggleTheme } = useTheme();
return (
<header className={isDark ? 'dark' : 'light'}>
<h1>برنامه من</h1>
<button onClick={toggleTheme}>
{isDark ? 'حالت روشن' : 'حالت تاریک'}
</button>
</header>
);
}
۵.۲ Redux Toolkit (RTK)
مدیریت سبد خرید با Redux Toolkit:
// store/cartSlice.js
import { createSlice } from '@reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: {
items: [],
totalQuantity: 0,
totalAmount: 0
},
reducers: {
addItem: (state, action) => {
const existingItem = state.items.find(item => item.id === action.payload.id);
if (existingItem) {
existingItem.quantity++;
existingItem.totalPrice = existingItem.quantity * existingItem.price;
} else {
state.items.push({
...action.payload,
quantity: 1,
totalPrice: action.payload.price
});
}
state.totalQuantity++;
state.totalAmount = state.items.reduce((sum, item) => sum + item.totalPrice, 0);
},
removeItem: (state, action) => {
const existingItem = state.items.find(item => item.id === action.payload);
if (existingItem) {
state.totalQuantity -= existingItem.quantity;
state.items = state.items.filter(item => item.id !== action.payload);
state.totalAmount = state.items.reduce((sum, item) => sum + item.totalPrice, 0);
}
},
clearCart: (state) => {
state.items = [];
state.totalQuantity = 0;
state.totalAmount = 0;
}
}
});
export const { addItem, removeItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer;
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './cartSlice';
export const store = configureStore({
reducer: {
cart: cartReducer
}
});
// CartComponent.js
import { useSelector, useDispatch } from 'react-redux';
import { addItem, removeItem, clearCart } from './store/cartSlice';
function Cart() {
const cart = useSelector(state => state.cart);
const dispatch = useDispatch();
return (
<div>
<h2>سبد خرید</h2>
<p>تعداد کل: {cart.totalQuantity}</p>
<p>مبلغ کل: {cart.totalAmount} تومان</p>
<button onClick={() => dispatch(addItem({ id: 1, name: 'محصول ۱', price: 50000 }))}>
افزودن محصول
</button>
<button onClick={() => dispatch(clearCart())}>
خالی کردن سبد خرید
</button>
<ul>
{cart.items.map(item => (
<li key={item.id}>
{item.name} - {item.price} تومان
(تعداد: {item.quantity})
<button onClick={() => dispatch(removeItem(item.id))}>حذف</button>
</li>
))}
</ul>
</div>
);
}
بخش ششم: React 19 و Server Components
۶.۱ Server Components در Next.js
کامپوننت سروری در Next.js 15:
// app/products/page.jsx - Server Component
import { getProducts } from '@/lib/products';
import ProductCard from '@/components/ProductCard';
export default async function ProductsPage() {
const products = await getProducts();
return (
<div className="container">
<h1>محصولات ما</h1>
<div className="grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
</div>
);
}
// components/ProductCard.jsx - Client Component
'use client';
import { useState } from 'react';
export default function ProductCard({ product }) {
const [isInCart, setIsInCart] = useState(false);
const addToCart = () => {
setIsInCart(true);
// منطق افزودن به سبد خرید
};
return (
<div className="product-card">
<h3>{product.name}</h3>
<p>{product.price} تومان</p>
<button
onClick={addToCart}
disabled={isInCart}
>
{isInCart ? 'افزوده شد' : 'افزودن به سبد خرید'}
</button>
</div>
);
}
بخش هفتم: React Compiler (Auto Memoization)
کامپایلر React - بهینهسازی خودکار:
// قبل از React 19 - نیاز به memoization دستی
import { memo, useCallback, useMemo } from 'react';
const ExpensiveComponent = memo(function ExpensiveComponent({ data, onItemClick }) {
const processedData = useMemo(() => {
return data.map(item => item * 2);
}, [data]);
const handleClick = useCallback((id) => {
onItemClick(id);
}, [onItemClick]);
return (
<div>
{processedData.map(item => (
<button key={item} onClick={() => handleClick(item)}>
{item}
</button>
))}
</div>
);
});
// بعد از React 19 - کامپایلر خودکار بهینه میکند
function ExpensiveComponent({ data, onItemClick }) {
const processedData = data.map(item => item * 2);
const handleClick = (id) => {
onItemClick(id);
};
return (
<div>
{processedData.map(item => (
<button key={item} onClick={() => handleClick(item)}>
{item}
</button>
))}
</div>
);
}
// فعالسازی کامپایلر در vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
['babel-plugin-react-compiler', { target: '19' }]
]
}
})
]
});
بخش هشتم: اکوسیستم React
۸.۱ کتابخانههای محبوب در اکوسیستم React
| دستهبندی | کتابخانه | کاربرد |
|---|---|---|
| رouting | React Router v7 | مدیریت مسیرها و ناوبری |
| state management | Redux Toolkit, Zustand | مدیریت state سراسری |
| فرمها | React Hook Form, Formik | مدیریت فرمها و اعتبارسنجی |
| API calls | React Query, SWR | مدیریت دادههای سرور |
| styling | Tailwind CSS, Styled Components | استایلدهی کامپوننتها |
| animation | Framer Motion | انیمیشنهای پیشرفته |
| UI components | Material UI, Ant Design | کامپوننتهای آماده |
۸.۲ فریمورکهای مبتنی بر React
⚡ Next.js
محبوبترین فریمورک React با قابلیت SSR, SSG, API Routes
🚀 Remix
فریمورک قدرتمند با تمرکز بر تجربه کاربری و فرمها
📱 React Native
توسعه اپلیکیشن موبایل برای iOS و Android با React
🖥️ Gatsby
فریمورک Static Site Generator برای وبسایتهای سریع
بخش نهم: بازار کار و فرصتهای شغلی
تحلیل بازار کار ایران در ۲۰۲۶:
| موقعیت شغلی | متوسط حقوق (تومان) | تقاضا | مهارتهای مورد نیاز |
|---|---|---|---|
| توسعهدهنده React ارشد | ۶۰-۹۰ میلیون | ⭐️⭐️⭐️⭐️⭐️ | React 19، Next.js، TypeScript، Redux |
| توسعهدهنده فرانتاند | ۴۵-۷۰ میلیون | ⭐️⭐️⭐️⭐️⭐️ | React، JavaScript، Tailwind، REST API |
| توسعهدهنده Full-Stack | ۷۰-۱۰۰ میلیون | ⭐️⭐️⭐️⭐️⭐️ | React، Next.js، Node.js، MongoDB |
| توسعهدهنده React Native | ۶۵-۹۵ میلیون | ⭐️⭐️⭐️⭐️ | React Native، Redux، Firebase |
فرصتهای شغلی بینالمللی:
- React Developer: $60,000 - $120,000 سالانه
- Senior Frontend Engineer: $80,000 - $150,000 سالانه
- Full-Stack (React + Node): $70,000 - $140,000 سالانه
- React Native Developer: $65,000 - $130,000 سالانه
نتیجهگیری: چرا React؟
React ۱۹ با معرفی ویژگیهای انقلابی مانند React Compiler، Server Components پایدار و Actions،又一次 ثابت کرد که چرا پادشاه بلامنازع دنیای فرانتاند است. این کتابخانه نه فقط یک ابزار، بلکه یک اکوسیستم کامل برای توسعه هر نوع برنامه وب است.
✅ برای مبتدیان:
- منابع آموزشی فراوان
- جامعه بزرگ و فعال
- منحنی یادگیری مناسب
- فرصتهای شغلی عالی
💼 برای کسبوکارها:
- کتابخانه پایدار و بالغ
- پشتیبانی فیسبوک و جامعه
- اکوسیستم غنی
- قابلیت مقیاسپذیری بالا
🚀 برای توسعهدهندگان:
- محبوبترین کتابخانه دنیا
- بازار کار عالی در ایران و جهان
- امکان توسعه وب، موبایل، دسکتاپ
- حقوقهای بالا
در درخت کد، ما React را به عنوان اصلیترین تکنولوژی فرانتاند برای پروژههایمان انتخاب کردهایم. ما همراه شما هستیم تا با آموزشهای تخصصی، پروژههای عملی و مشاورههای فنی، مسیر تبدیل شدن به یک توسعهدهنده حرفهای React را هموار کنیم.
🎯 راهنمای عملی شروع با React 19:
- مطالعه مستندات رسمی: React Documentation
- شرکت در دوره React.js درخت کد
- ساخت یک پروژه عملی (Todo App، فروشگاه کوچک)
- یادگیری Next.js برای پروژههای حرفهای
- عضویت در جامعه React ایران