Avancé30 min de lecture

Bonnes Pratiques React

Apprends les conventions, patterns et anti-patterns qui distinguent le code React professionnel du code amateur, couvrant l'organisation des composants, l'accessibilité, les performances et la maintenabilité.

Organisation des Composants et Structure des Fichiers

La façon dont tu organises tes fichiers a un impact direct sur la rapidité avec laquelle tu peux naviguer, comprendre et maintenir ta base de code. Il n'existe pas de structure unique « correcte », mais deux patterns se sont révélés efficaces à grande échelle :

1. Structure basée sur les fonctionnalités (recommandée pour les apps) :

Regroupe les fichiers par fonctionnalité, pas par type. Chaque dossier de fonctionnalité contient tout ce dont il a besoin :

html
src/
  features/
    auth/
      LoginForm.tsx
      SignupForm.tsx
      useAuth.ts
      auth.test.ts
      auth.types.ts
    dashboard/
      Dashboard.tsx
      DashboardChart.tsx
      useDashboardData.ts
    shared/
      components/
        Button.tsx
        Modal.tsx
      hooks/
        useDebounce.ts
        useLocalStorage.ts

2. Structure de composants plate (pour les petits projets) :

html
src/
  components/
    Button/
      Button.tsx
      Button.test.tsx
      Button.module.css
      index.ts
    Header/
      Header.tsx
      Header.test.tsx
      index.ts

Conventions clés :

  • Un composant par fichier. Le nom du fichier correspond au nom du composant : UserCard.tsx exporte UserCard.
  • Utilise un fichier barrel index.ts pour simplifier les imports : import { Button } from '@/components/Button'.
  • Co-localise les tests, styles et types avec le composant auquel ils appartiennent.
  • Sépare les composants de présentation (UI pure, reçoivent des données via props) des composants conteneurs (récupération de données, logique métier).
  • Garde les composants petits. Si un fichier de composant dépasse 200-300 lignes, c'est un signal pour extraire des sous-composants ou des hooks personnalisés.

Conventions de Nommage et Responsabilité Unique

Un nommage cohérent rend ta base de code lisible sans documentation :

Composants : PascalCase. Nomme-les d'après ce qu'ils rendent, pas ce qu'ils font.

html
UserCard.tsx      (bon — décrit ce qu'il rend)
FetchUser.tsx     (mauvais — décrit l'implémentation)

Hooks : camelCase avec le préfixe use. Nomme-les d'après ce qu'ils fournissent.

html
useAuth()         (bon — fournit l'état et les actions d'authentification)
useLocalStorage() (bon — fournit l'accès au localStorage)
useFetch()        (ok — générique, mais clair)

Gestionnaires d'événements : handle + nom de l'événement dans le composant, on + nom de l'événement pour les props.

jsx
// À l'intérieur du composant
function SearchBar({ onSearch }) {
  const handleSubmit = (e) => {
    e.preventDefault();
    onSearch(query);
  };
  // ...
}

Props booléennes : Utilise les préfixes is, has ou should.

jsx
<Modal isOpen={true} hasOverlay={true} shouldCloseOnEscape={true} />

Le Principe de Responsabilité Unique stipule que chaque composant ne devrait avoir qu'une seule raison de changer. Signes que ton composant en fait trop :

  • Il a plus de 3-4 variables d'état.
  • Il rend différentes UI basées sur de nombreuses conditions.
  • Il mélange la récupération de données avec le rendu.
  • Tu ne peux pas décrire ce qu'il fait en une phrase.

Extrais la logique dans des hooks personnalisés. Extrais l'UI dans des sous-composants. Le résultat est des composants plus faciles à tester, réutiliser et comprendre.

Directives de Gestion d'État

L'une des plus grandes sources de complexité dans les apps React est la mauvaise gestion de l'état. Suis ces directives pour garder l'état prévisible :

1. Commence avec l'état local. Commence toujours avec useState dans le composant qui a besoin des données. Ne remonte l'état que lorsque des composants frères doivent le partager.

jsx
// Bon — l'état est local au composant qui l'utilise
function SearchBar() {
  const [query, setQuery] = React.useState('');
  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

2. Ne remonte l'état que si nécessaire. Quand deux composants frères ont besoin des mêmes données, déplace l'état vers leur parent commun le plus proche :

jsx
function App() {
  const [user, setUser] = React.useState(null);
  return (
    <>
      <Header user={user} />
      <Profile user={user} onUpdate={setUser} />
    </>
  );
}

3. Utilise le contexte pour l'état profondément partagé. Quand les données doivent traverser de nombreux niveaux (thème, locale, authentification), utilise le pattern Provider :

jsx
<AuthProvider>
  <ThemeProvider>
    <App />
  </ThemeProvider>
</AuthProvider>

4. Utilise des stores externes pour un état complexe. Quand la logique d'état devient complexe (de nombreuses actions, état dérivé, flux asynchrones), utilise une bibliothèque de gestion d'état comme Zustand ou Redux Toolkit.

5. Dérive l'état, ne le synchronise pas. Si tu peux calculer une valeur à partir de l'état existant, ne la stocke pas séparément :

jsx
// Mauvais — synchronisation d'état dérivé
const [items, setItems] = React.useState([]);
const [count, setCount] = React.useState(0); // redondant !

// Bon — dérive-le
const [items, setItems] = React.useState([]);
const count = items.length; // calculé à chaque rendu

6. Évite l'état pour les valeurs de type ref. Les valeurs qui ne doivent pas déclencher de re-rendus (IDs de timers, valeurs précédentes, références DOM) appartiennent à useRef, pas useState.

Accessibilité dans React

L'accessibilité (a11y) n'est pas une réflexion après coup — c'est une exigence de qualité fondamentale. React facilite la construction d'interfaces accessibles quand tu suis ces pratiques :

1. Utilise des éléments HTML sémantiques :

jsx
// Mauvais
<div onClick={handleClick}>Clique-moi</div>

// Bon
<button onClick={handleClick}>Clique-moi</button>

Les éléments sémantiques viennent avec une gestion intégrée du clavier, de la gestion du focus et des annonces pour les lecteurs d'écran. Un <button> est focusable, répond à Enter/Espace, et est annoncé comme un bouton. Un <div> ne fait rien de tout ça.

2. Fournis toujours des alternatives textuelles :

jsx
<img src="avatar.jpg" alt="Photo de profil de Jane" />
<button aria-label="Fermer la boîte de dialogue"><XIcon /></button>

3. Structure les pages avec des landmarks :

jsx
function App() {
  return (
    <>
      <header><h1>Mon App</h1></header>
      <nav><a href="/">Accueil</a></nav>
      <main><p>Le contenu va ici</p></main>
      <footer><p>Informations de copyright</p></footer>
    </>
  );
}

Les lecteurs d'écran utilisent ces landmarks (<header>, <nav>, <main>, <footer>) pour permettre aux utilisateurs de sauter entre les sections de la page.

4. Gère le focus pour le contenu dynamique : Quand une modale s'ouvre, déplace le focus dedans. Quand elle se ferme, retourne le focus sur l'élément déclencheur :

jsx
const triggerRef = React.useRef();
const modalRef = React.useRef();

const openModal = () => {
  setIsOpen(true);
  // Focus la modale après le rendu
  setTimeout(() => modalRef.current?.focus(), 0);
};

const closeModal = () => {
  setIsOpen(false);
  triggerRef.current?.focus(); // Retourne le focus
};

5. Utilise les attributs ARIA quand le HTML natif est insuffisant :

jsx
<div role="alert" aria-live="polite">
  {errorMessage}
</div>

<button aria-expanded={isOpen} aria-controls="menu-content">
  Menu
</button>
<ul id="menu-content" role="menu" hidden={!isOpen}>
  <li role="menuitem">Option 1</li>
</ul>

6. Teste avec un clavier. Navigue dans toute ton interface en utilisant uniquement Tab, Shift+Tab, Enter, Espace, Échap et les flèches. Si quelque chose n'est pas atteignable ou utilisable, corrige-le.

Anti-Patterns Courants à Éviter

Reconnaître les anti-patterns est aussi important que connaître les bonnes pratiques. Voici les erreurs les plus courantes :

1. Prop drilling à travers de nombreuses couches :

jsx
// Mauvais — passer user à travers 5 niveaux
<App user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <UserMenu user={user} />
    </Sidebar>
  </Layout>
</App>

// Bon — utilise le contexte
<UserProvider>
  <App />
</UserProvider>

2. Énormes composants monolithiques : Si ton fichier de composant fait 500+ lignes, il en fait trop. Extrais des sous-composants et des hooks.

3. useEffect inutile :

jsx
// Mauvais — useEffect pour transformer les données
useEffect(() => {
  setFilteredItems(items.filter(i => i.active));
}, [items]);

// Bon — calcule pendant le rendu
const filteredItems = items.filter(i => i.active);

useEffect est pour se synchroniser avec des systèmes externes (appels API, abonnements, manipulation DOM). Si tu l'utilises pour dériver l'état, tu compliques les choses inutilement.

4. Keys manquantes ou utilisation de l'index comme key :

jsx
// Mauvais — les keys d'index causent des bugs quand l'ordre de la liste change
{items.map((item, index) => <Item key={index} {...item} />)}

// Bon — utilise un identifiant stable et unique
{items.map(item => <Item key={item.id} {...item} />)}

5. Manipulation directe du DOM :

jsx
// Mauvais — contourner React
document.getElementById('title').textContent = 'Nouveau Titre';

// Bon — laisse React gérer le DOM
setTitle('Nouveau Titre');

6. Ne pas nettoyer les effets de bord :

jsx
// Mauvais — fuite mémoire
useEffect(() => {
  window.addEventListener('resize', handleResize);
}, []);

// Bon — fonction de nettoyage
useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

Checklist de performance :

  • Mémorise les calculs coûteux avec useMemo.
  • Mémorise les callbacks passés aux enfants avec useCallback.
  • Utilise React.memo pour les composants qui se re-rendent avec les mêmes props.
  • Charge paresseusement les composants lourds avec React.lazy et Suspense.
  • Profile avec React DevTools avant d'optimiser — ne devine pas.

Exemple d'App Bien Structurée

html
<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
  // Chaque composant a une responsabilité unique
  // Éléments HTML sémantiques pour l'accessibilité
  // Conventions de nommage claires

  function Header() {
    return (
      <header id="header">
        <h1>Mon App</h1>
      </header>
    );
  }

  function Main() {
    return (
      <main id="main">
        <p>Contenu</p>
      </main>
    );
  }

  function Footer() {
    return (
      <footer id="footer">
        <p>&copy; 2024</p>
      </footer>
    );
  }

  // App compose des composants indépendants
  function App() {
    return (
      <>
        <Header />
        <Main />
        <Footer />
      </>
    );
  }

  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>

Lequel des éléments suivants est un anti-pattern dans React ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.