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é.
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 :
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.ts2. Structure de composants plate (pour les petits projets) :
src/
components/
Button/
Button.tsx
Button.test.tsx
Button.module.css
index.ts
Header/
Header.tsx
Header.test.tsx
index.tsConventions clés :
UserCard.tsx exporte UserCard.index.ts pour simplifier les imports : import { Button } from '@/components/Button'.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.
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.
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.
// À 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.
<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 :
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.
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.
// 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 :
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 :
<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 :
// 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 rendu6. É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.
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 :
// 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 :
<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 :
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 :
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 :
<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.
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 :
// 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 :
// 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 :
// 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 :
// 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 :
// 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 :
useMemo.useCallback.React.memo pour les composants qui se re-rendent avec les mêmes props.React.lazy et Suspense.<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>© 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 ?