Conception de composants : de l'atome à l'univers
La modularité ne se résume pas simplement à découper une page en morceaux. C'est une capacité d'abstraction systémique – comme assembler des LEGO, où chaque pièce a une responsabilité claire et des interfaces standardisées, permettant une composition stable.
Par exemple, dans le projet Cosmos de domotique, nous définissons une bibliothèque de composants atomiques :
// Indicateur de batterie (niveau atomique)
const IndicateurBatterie = ({ niveau, enCharge }) => (
<div className={`batterie ${enCharge ? 'en-charge' : ''}`}>
<span style={{ width: `${niveau}%` }} />
</div>
);
// Carte d'appareil (niveau moléculaire)
const CarteAppareil = ({ appareil }) => (
<div className="carte-appareil">
<h3>{appareil.nom}</h3>
<IndicateurBatterie niveau={appareil.batterie} enCharge={appareil.enCharge} />
<button onClick={() => basculerAlimentation(appareil.id)}>
{appareil.allume ? 'Éteindre' : 'Allumer'}
</button>
</div>
);
Remarquez que IndicateurBatterie gère uniquement l'affichage de la barre de batterie, tandis que CarteAppareil organise la structure des informations. Il n'y a aucun couplage entre eux ; changer la mise en page ou le style ne les affecte pas mutuellement. Ce design à haute cohésion et faible couplage est essentiel pour une collaboration d'équipe sans accroc.
Composants fonctionnels vs composants de classe : une révolution silecnieuse
Les composants fonctionnels avec les hooks ont provoqué un changement de paradigme. Par exemple, au lieu d'utiliser des méthodes de cycle de vie dans une classe, avec les hooks nous pouvons écrire :
// Composant de classe : plusieurs méthodes de cycle de vie
componentDidMount() {
recupererUtilisateur();
}
componentDidUpdate(prevProps) {
if (prevProps.utilisateurId !== this.props.utilisateurId) {
recupererUtilisateur();
}
}
// Composant fonctionnel + useEffect
useEffect(() => {
recupererUtilisateur();
}, [utilisateurId]);
La différence clé : les composants de classe vous disent "quand faire quoi", tandis que les composants fonctionnels vous disent "de quelles données dépendre". Ce dernier est plus intuitif.
Attention aux pièges des closures :
function Compteur() {
const [nombre, setNombre] = useState(0);
function mauvaisIncre() {
setTimeout(() => {
setNombre(nombre + 1); // Capture une valeur obsolète
}, 1000);
}
function bonIncre() {
setTimeout(() => {
setNombre(n => n + 1); // Mise à jour fonctionnelle
}, 1000);
}
}
DOM virtuel : non seulement rapide, mais intelligemment rapide
Le DOM virtuel est souvent cité pour la performance de React, mais une utilisation abusive peut la détériorer. Par exemple, évitez de créer des fonctions fléchées à l'intérieur des rendus :
// Mauvaise pratique
function MauvaiseListe({ elements }) {
return (
<ul>
{elements.map(element => (
<li key={element.id}>
<button onClick={() => supprimer(element.id)}>Supprimer</button>
</li>
))}
</ul>
);
}
// Bonne pratique
function BonneListe({ elements, onSupprimer }) {
return (
<ul>
{elements.map(element => (
<li key={element.id}>
<button onClick={() => onSupprimer(element.id)}>Supprimer</button>
</li>
))}
</ul>
);
}
Utilisez useCallback pour stabiliser les références des fonctions de rappel si nécessaire.
Algorithme de diff : le cerveau de React
L'algorithme de diff de React repose sur deux hypothèses : les mouvements entre niveaux sont rares, et les clés identifient de manière unique les éléments. Cela réduit la complexité à O(n).
Architecture Fiber : rendu interruptible
Depuis React 16, l'architecture Fiber permet au rendu d'être asynchrone et interruptible, garantissant une interface utilisateur fluide même lors de mises à jour importantes. Pour de gros traitements de données, utilisez requestIdleCallback pour éviter de bloquer le thread principal.
JSX : plus qu'un sucre syntaxique
JSX étend les capacités de JavaScript pour décrire des interfaces utilisateur de manière déclarative. Par exemple, un fragment JSX est compilé en appels à React.createElement. Les fonctionnalités avancées comme les Frgaments et les Portails améliorent la flexibilité.
Modèles avancés
Les Portails permettent de rendre des composants à un endroit différent dans le DOM, comme pour les modales :
function Modale({ enfants }) {
return ReactDOM.createPortal(
<div className="fond-modale">
{enfants}
</div>,
document.body
);
}
Gestion d'état : de la confusion à l'ordre
Pour les états globaux tels que le thème ou l'authentification, l'API Context est utile :
const ContexteTheme = createContext();
function FournisseurTheme({ enfants }) {
const [sombre, setSombre] = useState(false);
const valeur = useMemo(() => ({
sombre,
basculer: () => setSombre(s => !s)
}), [sombre]);
return (
<ContexteTheme.Provider value={valeur}>
{enfants}
</ContexteTheme.Provider>
);
}
function EnTete() {
const { sombre, basculer } = useContext(ContexteTheme);
return (
<header className={sombre ? 'sombre' : ''}>
<button onClick={basculer}>Changer le thème</button>
</header>
);
}
Encapsulez la logique réutilisable dans des hooks personnalisés, comme pour la récupération de données :
import { useState, useEffect } from 'react';
export function useDonneesApi<T>(url: string): {
donnees: T | null;
chargement: boolean;
erreur: string | null;
} {
const [donnees, setDonnees] = useState<T | null>(null);
const [chargement, setChargement] = useState(true);
const [erreur, setErreur] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(setDonnees)
.catch(err => setErreur(err.message))
.finally(() => setChargement(false));
}, [url]);
return { donnees, chargement, erreur };
}
// Utilisation
const { donnees, chargement } = useDonneesApi<Utilisateur[]>('/api/utilisateurs');
Routage et ingénierie : le squelette pour la collaboration à grande échelle
React Router v6 offre une syntaxe plus propre pour le routage imbriqué :
<Routes>
<Route path="/" element={<Accueil />} />
<Route path="/utilisateurs/:id" element={<Profil />} />
<Route path="*" element={<NonTrouve />} />
</Routes>
Pour les outils de build, Webpack offre plus de personnalisation pour les applications d'entreprise, tandis que Parcel est plus simple pour les prototypes. TypeScript améliore la maintenabilité avec des types statiques.
Tests et déploiement : la dernière ligne de défense pour la qualité
Utilisez Jest et React Testing Library pour tester le comportement utilisateur, pas les détails d'implémentation :
test('échec de connexion affiche un message d\'erreur', async () => {
render(<Connexion />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'mauvais@cosmos.com' }
});
fireEvent.click(screen.getByRole('button', { name: 'Connexion' }));
expect(await screen.findByText('Identifiants incorrects')).toBeInTheDocument();
});
Intégrez des pipelines CI/CD avec GitHub Actions pour des déploiements automatisés, en utilisant des variables d'environnement pour gérer les configurations par environnement.