أساسيات React.js
أفضل ممارسات React وتنظيم الكود
هيكل المشروع والتنظيم
هيكل المشروع المنظم جيداً أمر بالغ الأهمية للصيانة وقابلية التوسع والتعاون الجماعي. دعنا نستكشف الأنماط المعيارية في الصناعة لتنظيم تطبيقات React.
هيكل المجلدات الموصى به
src/
├── assets/ # ملفات ثابتة (صور، خطوط، أيقونات)
│ ├── images/
│ ├── fonts/
│ └── icons/
├── components/ # مكونات واجهة المستخدم القابلة لإعادة الاستخدام
│ ├── common/ # مكونات مشتركة
│ │ ├── Button/
│ │ │ ├── Button.jsx
│ │ │ ├── Button.module.css
│ │ │ ├── Button.test.jsx
│ │ │ └── index.js
│ │ ├── Input/
│ │ └── Modal/
│ └── layout/ # مكونات التخطيط
│ ├── Header/
│ ├── Footer/
│ └── Sidebar/
├── features/ # وحدات قائمة على الميزات
│ ├── auth/
│ │ ├── components/
│ │ │ ├── LoginForm.jsx
│ │ │ └── RegisterForm.jsx
│ │ ├── hooks/
│ │ │ └── useAuth.js
│ │ ├── services/
│ │ │ └── authService.js
│ │ ├── store/
│ │ │ └── authSlice.js
│ │ └── index.js
│ ├── products/
│ └── dashboard/
├── hooks/ # خطافات مخصصة
│ ├── useLocalStorage.js
│ ├── useDebounce.js
│ └── useFetch.js
├── services/ # خدمات API والخدمات الخارجية
│ ├── api.js
│ ├── axios.js
│ └── websocket.js
├── store/ # إدارة الحالة العامة
│ ├── index.js
│ ├── rootReducer.js
│ └── middleware.js
├── styles/ # أنماط عامة
│ ├── variables.css
│ ├── reset.css
│ └── utilities.css
├── utils/ # وظائف مساعدة
│ ├── formatters.js
│ ├── validators.js
│ └── constants.js
├── routes/ # إعدادات المسارات
│ ├── index.jsx
│ ├── ProtectedRoute.jsx
│ └── routes.config.js
├── App.jsx
├── index.jsx
└── config.js
فوائد الهيكل:
- قائم على الميزات: الكود ذو الصلة مجمع معاً (features/)
- قابلية التوسع: سهولة إضافة ميزات جديدة بدون فوضى
- قابلية الاكتشاف: المطورون يمكنهم العثور على الكود بسرعة
- قابلية إعادة الاستخدام: المكونات الشائعة منفصلة بوضوح
اصطلاحات التسمية
التسمية المتسقة تجعل الكود أكثر قابلية للقراءة والصيانة:
// المكونات - PascalCase
function UserProfile() { }
function ProductCard() { }
const NavigationMenu = () => { };
// الملفات - تطابق اسم المكون
UserProfile.jsx
ProductCard.tsx
NavigationMenu.js
// الخطافات المخصصة - camelCase مع بادئة 'use'
function useAuth() { }
function useFetchData() { }
function useLocalStorage() { }
// الوظائف المساعدة - camelCase
function formatCurrency() { }
function validateEmail() { }
function debounce() { }
// الثوابت - SCREAMING_SNAKE_CASE
const API_BASE_URL = 'https://api.example.com';
const MAX_RETRIES = 3;
const DEFAULT_TIMEOUT = 5000;
// وحدات CSS - camelCase
import styles from './Button.module.css';
<button className={styles.primaryButton}>
// التعدادات/الكائنات - PascalCase أو UPPER_CASE
const UserRole = {
ADMIN: 'admin',
USER: 'user',
GUEST: 'guest'
};
const API_ENDPOINTS = {
USERS: '/api/users',
PRODUCTS: '/api/products'
};
أنماط تنظيم المكونات
النمط 1: هيكل مجلد المكون
كل مكون يحصل على مجلد خاص به مع ملفات ذات صلة:
Button/
├── Button.jsx # المكون الرئيسي
├── Button.module.css # أنماط خاصة بالمكون
├── Button.test.jsx # اختبارات الوحدة
├── Button.stories.jsx # قصص Storybook
├── useButton.js # خطاف خاص بالمكون
└── index.js # واجهة برمجية عامة (barrel export)
// index.js - Barrel export
export { Button } from './Button';
export { default } from './Button';
// الاستخدام - استيرادات نظيفة
import Button from '@/components/Button';
import { Button } from '@/components/Button';
النمط 2: فصل الحاوية/العرض
فصل المنطق (الحاويات) عن واجهة المستخدم (العرض):
// UserListContainer.jsx - المنطق وجلب البيانات
import { useEffect, useState } from 'react';
import { UserList } from './UserList';
import { fetchUsers } from './services';
export function UserListContainer() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers()
.then(setUsers)
.finally(() => setLoading(false));
}, []);
const handleDelete = (id) => {
setUsers(users.filter(u => u.id !== id));
};
return (
<UserList
users={users}
loading={loading}
onDelete={handleDelete}
/>
);
}
// UserList.jsx - عرض نقي
export function UserList({ users, loading, onDelete }) {
if (loading) return <Spinner />;
return (
<ul>
{users.map(user => (
<UserItem
key={user.id}
user={user}
onDelete={onDelete}
/>
))}
</ul>
);
}
استراتيجيات تقسيم الكود
قسّم الكود لتقليل حجم الحزمة الأولية وتحسين أوقات التحميل:
// 1. تقسيم الكود القائم على المسار
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// 2. تقسيم الكود القائم على المكون
const HeavyChart = lazy(() => import('./components/HeavyChart'));
function Analytics() {
return (
<div>
<h1>التحليلات</h1>
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart data={chartData} />
</Suspense>
</div>
);
}
// 3. تقسيم كود المكتبة
const Markdown = lazy(() =>
import('react-markdown').then(module => ({
default: module.default
}))
);
فوائد تقسيم الكود:
- تحميل أولي أسرع: المستخدمون يحملون فقط ما يحتاجونه
- تخزين مؤقت أفضل: المسارات غير المتغيرة تبقى مخزنة مؤقتاً
- التحسين التدريجي: التطبيق الأساسي يحمّل أولاً، الميزات تحمّل حسب الطلب
- نطاق ترددي مخفض: مهم لمستخدمي الهواتف المحمولة
تصديرات Barrel للاستيرادات النظيفة
استخدم ملفات index لإنشاء مسارات استيراد نظيفة ومنظمة:
// components/common/index.js - Barrel export
export { Button } from './Button';
export { Input } from './Input';
export { Modal } from './Modal';
export { Card } from './Card';
export { Spinner } from './Spinner';
// قبل - استيرادات فوضوية
import { Button } from './components/common/Button/Button';
import { Input } from './components/common/Input/Input';
import { Modal } from './components/common/Modal/Modal';
// بعد - استيرادات نظيفة
import { Button, Input, Modal } from '@/components/common';
// features/auth/index.js - Barrel للميزة
export { LoginForm } from './components/LoginForm';
export { RegisterForm } from './components/RegisterForm';
export { useAuth } from './hooks/useAuth';
export { authService } from './services/authService';
// الاستخدام
import { LoginForm, useAuth } from '@/features/auth';
تحذير تصديرات Barrel: بينما تحسن تصديرات barrel قابلية قراءة الاستيراد، يمكن أن تؤثر على tree-shaking. المجمّعات الحديثة تتعامل مع هذا بشكل جيد، لكن كن على علم:
- لا تعيد تصدير كل شيء في المكتبات الكبيرة
- استخدم التصديرات المسماة، وليس التصديرات الافتراضية في barrels
- اختبر حجم الحزمة للتأكد من عمل tree-shaking
الأسماء المستعارة للمسار للاستيرادات الأنظف
قم بتكوين الأسماء المستعارة للمسار لتجنب جحيم المسار النسبي:
// jsconfig.json أو tsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"@/*": ["*"],
"@components/*": ["components/*"],
"@features/*": ["features/*"],
"@hooks/*": ["hooks/*"],
"@utils/*": ["utils/*"],
"@services/*": ["services/*"],
"@store/*": ["store/*"]
}
}
}
// قبل - كابوس المسار النسبي
import Button from '../../../../components/common/Button';
import { useAuth } from '../../../features/auth/hooks/useAuth';
// بعد - استيرادات مطلقة نظيفة
import Button from '@/components/common/Button';
import { useAuth } from '@/features/auth/hooks/useAuth';
الإعدادات الخاصة بالبيئة
// config/index.js - إعدادات مركزية
const config = {
apiUrl: process.env.REACT_APP_API_URL || 'http://localhost:3000',
apiTimeout: 10000,
environment: process.env.NODE_ENV,
features: {
enableAnalytics: process.env.REACT_APP_ENABLE_ANALYTICS === 'true',
enableNotifications: true,
maxUploadSize: 5 * 1024 * 1024, // 5MB
},
social: {
facebookAppId: process.env.REACT_APP_FACEBOOK_APP_ID,
googleClientId: process.env.REACT_APP_GOOGLE_CLIENT_ID,
},
};
export default config;
// الاستخدام
import config from '@/config';
if (config.features.enableAnalytics) {
initAnalytics();
}
fetch(`${config.apiUrl}/users`, {
timeout: config.apiTimeout
});
أفضل ممارسات تكوين المكونات
// ❌ سيئ - كابوس تمرير Props
function App() {
const [user, setUser] = useState(null);
return <Layout user={user} setUser={setUser}>
<Dashboard user={user} setUser={setUser}>
<Profile user={user} setUser={setUser} />
</Dashboard>
</Layout>;
}
// ✅ جيد - Context للحالة المشتركة
const UserContext = createContext();
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
<Layout>
<Dashboard>
<Profile />
</Dashboard>
</Layout>
</UserContext.Provider>
);
}
// ❌ سيئ - مكون متجانس
function UserDashboard() {
// 500 سطر من الكود
// جلب، حالة، تحقق، واجهة مستخدم، كل شيء
}
// ✅ جيد - مكونات مركبة
function UserDashboard() {
const { user } = useUser();
return (
<>
<UserHeader user={user} />
<UserStats userId={user.id} />
<UserActivity userId={user.id} />
<UserSettings user={user} />
</>
);
}
التوثيق والتعليقات
/**
* يعرض مكون زر قابل للتخصيص مع متغيرات متعددة.
*
* @param {Object} props - خصائص المكون
* @param {string} props.variant - متغير نمط الزر ('primary', 'secondary', 'danger')
* @param {string} props.size - حجم الزر ('small', 'medium', 'large')
* @param {boolean} props.disabled - ما إذا كان الزر معطلاً
* @param {boolean} props.loading - يظهر دوار التحميل
* @param {function} props.onClick - معالج النقر
* @param {React.ReactNode} props.children - محتوى الزر
*
* @example
* <Button variant="primary" size="large" onClick={handleSubmit}>
* إرسال النموذج
* </Button>
*/
export function Button({
variant = 'primary',
size = 'medium',
disabled = false,
loading = false,
onClick,
children
}) {
// التنفيذ
}
// PropTypes للتحقق أثناء التشغيل
Button.propTypes = {
variant: PropTypes.oneOf(['primary', 'secondary', 'danger']),
size: PropTypes.oneOf(['small', 'medium', 'large']),
disabled: PropTypes.bool,
loading: PropTypes.bool,
onClick: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
};
تمرين 1: أعد هيكلة مشروع React موجود:
- أعد تنظيم الملفات باستخدام هيكل المجلدات الموصى به
- نفّذ تصديرات barrel للمكونات الشائعة
- قم بإعداد الأسماء المستعارة للمسار في إعدادات البناء
- أنشئ ملف إعدادات مركزي لمتغيرات البيئة
تمرين 2: نفّذ تقسيم الكود:
- حدد أثقل 5 مكونات في تطبيقك
- نفّذ تقسيم الكود القائم على المسار لجميع الصفحات
- أضف التقسيم القائم على المكون للمخططات/المحررات الثقيلة
- قس حجم الحزمة قبل وبعد
- استهدف تخفيض 30% في حجم الحزمة الأولية
تمرين 3: أنشئ مكتبة مكونات:
- ابنِ 5-10 مكونات قابلة لإعادة الاستخدام (Button, Input, Card, إلخ.)
- استخدم تسمية وهيكل مجلدات متسقة
- أضف PropTypes أو أنواع TypeScript
- اكتب تعليقات JSDoc لكل مكون
- أنشئ index.js مع تصديرات barrel