Bonnes Pratiques
Les habitudes qui font la différence entre un projet amateur et pro.
9.1Architecture de fichiers
Une structure claire, tu la comprends en un coup d'œil :
src/
├── components/
│ ├── ui/ # Composants génériques (Button, Input, Modal)
│ ├── layout/ # Header, Footer, Sidebar
│ └── features/ # Composants métier (CartItem, ProductCard)
├── pages/ # Pages de l'app (Home, About, Dashboard)
├── hooks/ # Custom hooks
├── contexts/ # Providers Context
├── services/ # Appels API (fetch, axios)
├── types/ # Types TypeScript partagés
├── utils/ # Fonctions utilitaires
└── App.tsx9.2Composants : petits et focalisés
Un composant ne devrait faire qu'une seule chose. Si tu dois scroller pour voir tout le composant, il est trop gros :
// ❌ MAUVAIS : composant géant qui fait tout
function ProductPage() {
// 20 lignes de state...
// 15 lignes de useEffect...
// 10 lignes de handlers...
return (
<div>
{/* 200 lignes de JSX... */}
</div>
)
}
// ✅ BON : découpé en composants focalisés
function ProductPage() {
const product = useProduct()
return (
<div>
<ProductHeader product={product} />
<ProductGallery images={product.images} />
<ProductInfo product={product} />
<AddToCartForm product={product} />
<ProductReviews productId={product.id} />
</div>
)
}9.3Custom Hooks : extraire la logique
Dès qu'une logique (state + effect) est réutilisable, extrais-la dans un hook custom :
// hooks/use-fetch.ts - Hook réutilisable
import { useState, useEffect } from 'react'
export function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
async function fetchData() {
try {
setLoading(true)
const res = await fetch(url)
if (!res.ok) throw new Error(`Erreur ${res.status}`)
const json = await res.json()
if (!cancelled) setData(json)
} catch (err) {
if (!cancelled) setError(err instanceof Error ? err.message : 'Erreur')
} finally {
if (!cancelled) setLoading(false)
}
}
fetchData()
return () => { cancelled = true }
}, [url])
return { data, loading, error }
}
// Utilisation : ultra simple !
function Users() {
const { data: users, loading, error } = useFetch<User[]>('/api/users')
if (loading) return <p>Chargement...</p>
if (error) return <p>Erreur : {error}</p>
return users?.map(u => <p key={u.id}>{u.name}</p>)
}9.4Performance : éviter les re-renders inutiles
React.memo
Empêche un composant de re-render si ses props n'ont pas changé. Utile pour les composants lourds (listes, graphiques).
useMemo / useCallback
Mémorise les calculs coûteux et les fonctions pour éviter de les recréer à chaque rendu.
Découper le state
Un gros state dans le parent cause le re-render de tous les enfants. Mets le state au plus proche de là où il est utilisé.
// Règle d'or : state au plus proche
// ❌ State dans le parent → tout re-rend
function Page() {
const [searchQuery, setSearchQuery] = useState("")
return (
<div>
<SearchBar value={searchQuery} onChange={setSearchQuery} />
<HeavyProductList /> {/* Re-rend à chaque frappe ! */}
<Footer /> {/* Re-rend aussi ! */}
</div>
)
}
// ✅ State dans le composant qui l'utilise
function Page() {
return (
<div>
<SearchBar /> {/* Gère son propre state */}
<HeavyProductList /> {/* Tranquille */}
<Footer /> {/* Tranquille */}
</div>
)
}9.5Nommage et conventions
| Élément | Convention | Exemple |
|---|---|---|
| Composants | PascalCase | ProductCard |
| Fichiers composants | PascalCase ou kebab-case | ProductCard.tsx |
| Hooks | camelCase avec "use" | useAuth, useFetch |
| Handlers | handle + Action | handleSubmit, handleClick |
| Props boolean | is/has/can + Adjectif | isOpen, hasError |
9.6Checklist avant de publier
- Tous les composants ont un nom descriptif
- Pas de
console.logrestant en production - Les
useEffectont des cleanup quand nécessaire - Les listes ont des
keyuniques et stables - Le state est au plus proche de son utilisation
- Les formulaires sont contrôlés et validés
- Les erreurs sont gérées (try/catch, Error Boundaries)
- L'app est responsive et accessible