0%
تطوير تطبيقات حديثة باستخدام React

تطوير تطبيقات حديثة باستخدام React

دليل شامل لإنشاء تطبيقات ويب عالية الأداء وسهلة الصيانة باستخدام React.

I

InSkillCoach

· دقيقة

تطوير تطبيقات حديثة باستخدام React

تُعدّ React واحدة من أشهر مكتبات JavaScript لبناء واجهات المستخدم. طوّرتها شركة Facebook (Meta) وتتولى صيانتها، وهي تتيح بناء تطبيقات ويب معقدة باستخدام مكوّنات قابلة لإعادة الاستخدام وإدارة فعّالة للحالة.

لماذا نختار React؟

  • النهج التصريحي: تصف الشكل الذي ينبغي أن تظهر به واجهتك، وتتولى React تحديثها
  • قائمة على المكوّنات: أنشئ عناصر واجهة مغلّفة وقابلة لإعادة الاستخدام
  • منظومة غنية: مجموعة واسعة من المكتبات والأدوات المكمّلة
  • مجتمع كبير: دعم وموارد وتحديثات منتظمة
  • أداء محسّن: Virtual DOM وعملية المطابقة (reconciliation) لتحديثات فعّالة
  • معتمدة لدى كبريات الشركات: Facebook وInstagram وNetflix وAirbnb وغيرها

إعداد مشروع React

الإنشاء باستخدام Create React App

Create React App أداة رسمية تتيح بدء مشروع React بسرعة:

# Installation
npx create-react-app mon-application

# Démarrage
cd mon-application
npm start

استخدام Vite (بديل حديث)

يوفّر Vite أوقات بناء أسرع وتجربة تطوير أفضل:

# Installation
npm create vite@latest mon-application -- --template react

# Démarrage
cd mon-application
npm install
npm run dev

أساسيات React

المكوّنات

المكوّنات هي اللبنات الأساسية لأي تطبيق React:

// Composant fonctionnel
function Salutation(props) {
  return <h1>Bonjour, {props.nom} !</h1>;
}

// Utilisation
<Salutation nom="Alice" />

JSX

JSX امتداد لصيغة اللغة يتيح كتابة HTML داخل JavaScript:

const element = <h1>Bonjour, monde !</h1>;

// Avec expressions JavaScript
const nom = "Alice";
const element = <h1>Bonjour, {nom} !</h1>;

// Avec attributs
const element = <img src={user.avatarUrl} alt="Avatar" />;

// Éléments imbriqués
const element = (
  <div>
    <h1>Titre</h1>
    <p>Paragraphe</p>
  </div>
);

الخصائص (Props)

تتيح الخصائص (props) تمرير البيانات من مكوّن أب إلى مكوّن ابن:

// Définition du composant
function Carte(props) {
  return (
    <div className="carte">
      <h2>{props.titre}</h2>
      <p>{props.description}</p>
      {props.children}
    </div>
  );
}

// Utilisation
<Carte 
  titre="Mon titre" 
  description="Ma description"
>
  <button>En savoir plus</button>
</Carte>

الحالة (State)

تتيح الحالة تخزين بيانات يمكن أن تتغير بمرور الوقت:

import { useState } from 'react';

function Compteur() {
  // Déclaration d'un état
  const [compteur, setCompteur] = useState(0);
  
  return (
    <div>
      <p>Vous avez cliqué {compteur} fois</p>
      <button onClick={() => setCompteur(compteur + 1)}>
        Cliquez ici
      </button>
    </div>
  );
}

دورة الحياة مع الـ Hooks

تتيح الـ Hooks استخدام الحالة وميزات React الأخرى داخل المكوّنات الوظيفية:

import { useState, useEffect } from 'react';

function ExempleEffet() {
  const [compteur, setCompteur] = useState(0);
  
  // Similaire à componentDidMount et componentDidUpdate
  useEffect(() => {
    // Met à jour le titre du document
    document.title = `Vous avez cliqué ${compteur} fois`;
    
    // Fonction de nettoyage (similaire à componentWillUnmount)
    return () => {
      document.title = 'React App';
    };
  }, [compteur]); // S'exécute uniquement si compteur change
  
  return (
    <div>
      <p>Vous avez cliqué {compteur} fois</p>
      <button onClick={() => setCompteur(compteur + 1)}>
        Cliquez ici
      </button>
    </div>
  );
}

إدارة الأحداث

تستخدم React صيغة مشابهة لـ HTML لإدارة الأحداث:

function Bouton() {
  function handleClick(e) {
    e.preventDefault();
    console.log('Le bouton a été cliqué');
  }
  
  return (
    <button onClick={handleClick}>
      Cliquez ici
    </button>
  );
}

العرض الشرطي

يمكنك عرض عناصر مختلفة بحسب شروط معينة:

function Connexion(props) {
  const estConnecte = props.estConnecte;
  
  return (
    <div>
      {estConnecte ? (
        <BoutonDeconnexion />
      ) : (
        <BoutonConnexion />
      )}
    </div>
  );
}

// Autre approche
function Message(props) {
  const nonLu = props.messagesNonLus.length;
  
  return (
    <div>
      <h1>Bonjour !</h1>
      {nonLu > 0 && (
        <p>Vous avez {nonLu} message(s) non lu(s).</p>
      )}
    </div>
  );
}

القوائم والمفاتيح

لعرض قوائم من العناصر، استخدم map() ومفاتيح فريدة:

function ListeArticles(props) {
  const articles = props.articles;
  
  return (
    <ul>
      {articles.map((article) => (
        <li key={article.id}>
          {article.titre}
        </li>
      ))}
    </ul>
  );
}

الإدارة المتقدمة للحالة

Context API

تتيح Context API مشاركة البيانات بين المكوّنات دون المرور عبر الـ props:

// Création du contexte
const ThemeContext = React.createContext('light');

// Fournisseur de contexte
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// Consommation du contexte avec useContext
function Bouton() {
  const theme = useContext(ThemeContext);
  
  return (
    <button className={theme}>
      Je suis stylisé selon le thème {theme}
    </button>
  );
}

useReducer لإدارة الحالة المعقدة

يُفضَّل استخدام useReducer على useState عندما تكون الحالات معقدة:

import { useReducer } from 'react';

// Définition du reducer
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { compteur: state.compteur + 1 };
    case 'decrement':
      return { compteur: state.compteur - 1 };
    default:
      throw new Error();
  }
}

function Compteur() {
  // Utilisation du reducer
  const [state, dispatch] = useReducer(reducer, { compteur: 0 });
  
  return (
    <div>
      Compteur: {state.compteur}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

مكتبات إدارة الحالة

بالنسبة إلى التطبيقات الأكثر تعقيدًا، يمكنك استخدام مكتبات متخصصة:

Redux

// Actions
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// Action creators
function increment() {
  return { type: INCREMENT };
}

// Reducer
function compteurReducer(state = { valeur: 0 }, action) {
  switch (action.type) {
    case INCREMENT:
      return { valeur: state.valeur + 1 };
    case DECREMENT:
      return { valeur: state.valeur - 1 };
    default:
      return state;
  }
}

// Store
const store = createStore(compteurReducer);

// Composant connecté
function Compteur({ valeur, increment, decrement }) {
  return (
    <div>
      <span>{valeur}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

const mapStateToProps = state => ({
  valeur: state.valeur
});

const mapDispatchToProps = {
  increment,
  decrement
};

export default connect(mapStateToProps, mapDispatchToProps)(Compteur);

Zustand (بديل حديث)

import create from 'zustand';

// Création du store
const useStore = create(set => ({
  compteur: 0,
  increment: () => set(state => ({ compteur: state.compteur + 1 })),
  decrement: () => set(state => ({ compteur: state.compteur - 1 })),
}));

// Utilisation dans un composant
function Compteur() {
  const { compteur, increment, decrement } = useStore();
  
  return (
    <div>
      <span>{compteur}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

التوجيه باستخدام React Router

تتيح React Router إدارة التنقل داخل تطبيق React:

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Accueil</Link>
        <Link to="/a-propos">À propos</Link>
        <Link to="/contact">Contact</Link>
      </nav>
      
      <Routes>
        <Route path="/" element={<Accueil />} />
        <Route path="/a-propos" element={<APropos />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/produits/:id" element={<DetailProduit />} />
        <Route path="*" element={<PageNonTrouvee />} />
      </Routes>
    </BrowserRouter>
  );
}

// Accès aux paramètres d'URL
import { useParams } from 'react-router-dom';

function DetailProduit() {
  const { id } = useParams();
  
  return <h1>Détail du produit {id}</h1>;
}

النماذج في React

النماذج المتحكَّم بها

في النموذج المتحكَّم به، تتولى React إدارة حالة النموذج:

import { useState } from 'react';

function FormulaireConnexion() {
  const [email, setEmail] = useState('');
  const [motDePasse, setMotDePasse] = useState('');
  
  function handleSubmit(e) {
    e.preventDefault();
    console.log('Email:', email);
    console.log('Mot de passe:', motDePasse);
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          Email:
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
        </label>
      </div>
      <div>
        <label>
          Mot de passe:
          <input
            type="password"
            value={motDePasse}
            onChange={(e) => setMotDePasse(e.target.value)}
            required
          />
        </label>
      </div>
      <button type="submit">Se connecter</button>
    </form>
  );
}

مكتبات النماذج

بالنسبة إلى النماذج المعقدة، استخدم مكتبات مثل Formik أو React Hook Form:

// Avec React Hook Form
import { useForm } from 'react-hook-form';

function FormulaireInscription() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  
  const onSubmit = data => console.log(data);
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Nom</label>
        <input {...register('nom', { required: 'Le nom est requis' })} />
        {errors.nom && <p>{errors.nom.message}</p>}
      </div>
      
      <div>
        <label>Email</label>
        <input 
          {...register('email', { 
            required: 'L\'email est requis',
            pattern: {
              value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
              message: 'Adresse email invalide'
            }
          })} 
        />
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      
      <button type="submit">S'inscrire</button>
    </form>
  );
}

تحسين الأداء

React.memo

تتيح React.memo تفادي عمليات إعادة العرض غير الضرورية للمكوّنات الوظيفية:

const MonComposant = React.memo(function MonComposant(props) {
  // Votre composant
});

useMemo وuseCallback

تتيح هاتان الدالتان (hooks) حفظ القيم والدوال في الذاكرة:

import { useMemo, useCallback } from 'react';

function ExempleOptimisation({ a, b }) {
  // Mémorise le résultat du calcul
  const resultat = useMemo(() => {
    console.log('Calcul coûteux...');
    return a * b;
  }, [a, b]); // Recalcule uniquement si a ou b change
  
  // Mémorise la fonction
  const handleClick = useCallback(() => {
    console.log('Bouton cliqué avec résultat:', resultat);
  }, [resultat]); // Recrée uniquement si resultat change
  
  return (
    <div>
      <p>Résultat: {resultat}</p>
      <button onClick={handleClick}>Cliquer</button>
    </div>
  );
}

التحميل الكسول (Lazy Loading)

يتيح التحميل الكسول تقسيم تطبيقك إلى أجزاء أصغر:

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Chargement paresseux des composants
const Accueil = lazy(() => import('./pages/Accueil'));
const APropos = lazy(() => import('./pages/APropos'));
const Contact = lazy(() => import('./pages/Contact'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Chargement...</div>}>
        <Routes>
          <Route path="/" element={<Accueil />} />
          <Route path="/a-propos" element={<APropos />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

الاختبارات في React

اختبارات الوحدة باستخدام Jest وReact Testing Library

// Composant à tester
function Salutation({ nom }) {
  return <h1>Bonjour, {nom} !</h1>;
}

// Test
import { render, screen } from '@testing-library/react';
import Salutation from './Salutation';

test('affiche le message de salutation', () => {
  render(<Salutation nom="Alice" />);
  const element = screen.getByText(/bonjour, alice !/i);
  expect(element).toBeInTheDocument();
});

اختبارات التفاعل

import { render, screen, fireEvent } from '@testing-library/react';
import Compteur from './Compteur';

test('incrémente le compteur lorsque le bouton est cliqué', () => {
  render(<Compteur />);
  
  // Vérifier la valeur initiale
  expect(screen.getByText(/compteur: 0/i)).toBeInTheDocument();
  
  // Cliquer sur le bouton
  fireEvent.click(screen.getByText('+'));
  
  // Vérifier la nouvelle valeur
  expect(screen.getByText(/compteur: 1/i)).toBeInTheDocument();
});

نشر تطبيق React

البناء للإنتاج (Build)

# Avec Create React App
npm run build

# Avec Vite
npm run build

الاستضافة الثابتة

يمكنك نشر تطبيقك على منصات مثل:

  • Netlify: اسحب مجلد build وأفلته، أو اضبط نشرًا مستمرًا
  • Vercel: محسّنة لتطبيقات React، مع نشر تلقائي انطلاقًا من GitHub
  • GitHub Pages: مثالية للمشاريع الشخصية أو مفتوحة المصدر
  • Firebase Hosting: حل متكامل يشمل المصادقة وقاعدة البيانات

أفضل الممارسات

  1. نظّم مشروعك: رتّب ملفاتك بحسب الميزة أو النوع
  2. استخدم المكوّنات الوظيفية: فهي أبسط وأكثر كفاءة مع الـ Hooks
  3. تجنّب تمرير الـ props عبر مستويات عديدة (props drilling): استخدم Context أو مكتبة لإدارة الحالة
  4. اكتب الاختبارات: تأكد من أن تطبيقك يعمل كما هو متوقع
  5. حسّن الأداء: استخدم React.memo وuseMemo وuseCallback بحكمة
  6. اتبع مبادئ SOLID: أنشئ مكوّنات ذات مسؤولية واحدة
  7. استخدم TypeScript: أضف الكتابة الساكنة لتفادي الأخطاء
  8. وثّق مكوّناتك: استخدم أدوات مثل Storybook

الخلاصة

تُعدّ React مكتبة قوية تتيح إنشاء واجهات مستخدم حديثة وعالية الأداء. وبفضل مرونتها ونظامها البيئي الغني، تُعتبر خيارًا ممتازًا لتطوير تطبيقات الويب بمختلف أحجامها.

وبإتقان المفاهيم الأساسية وأفضل الممارسات المعروضة في هذه المقالة، ستتمكن من إنشاء تطبيقات React متينة وسهلة الصيانة وقابلة للتطوير.

InSkillCoach

نبذة عن InSkillCoach

خبير في التدريب والتقنيات

مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.

الشهادات:

  • AWS Certified Solutions Architect – Professional
  • شهادات Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
1.8k
115

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!