Écrire des hooks React : useState, useEffect, useReducer et hooks personnalisés
Apprenez à écrire correctement useState, useEffect, useReducer et des hooks personnalisés en TypeScript, ainsi que les moments où choisir TypeScript plutôt que du JavaScript pur s’avère vraiment avantageux.
Les erreurs de type détectées au moment de la compilation vous épargnent des sessions de débogage qui pourraient sinon durer jusqu’au petit matin, et c’est précisément pourquoi les équipes qui combinent React avec TypeScript considèrent les hooks typés comme une exigence fondamentale et non comme un luxe. Maîtriser la manière de définir le type des états, des effets, des réducteurs et des hooks personnalisés — ainsi que savoir quand recourir à TypeScript est la bonne décision dès le départ — fait partie des compétences pratiques essentielles pour développer des applications React fiables.
Pourquoi la sécurité de type doit faire partie de vos hooks
Les React Hooks ont déjà offert aux développeurs une manière plus propre et plus modulaire de structurer les composants. TypeScript ajoute une couche de sécurité par-dessus cette structure, permettant ainsi que les erreurs apparaissent pendant l’écriture du code plutôt qu’après que un utilisateur se heurte à une interface défectueuse en production.
Dès leur conception, les hooks ont été conçus pour rendre prévisible une logique réutilisable — un point souligné par Dan Abramov lorsqu’il aborde la philosophie de conception de React. La contribution de TypeScript consiste à rendre cette prévisibilité explicite et vérifiable, plutôt qu’à la garder uniquement en tête.
Spécifier correctement useState
Dans la plupart des cas courants, TypeScript détermine automatiquement le type de l’état sans aucune aide :
// inferred as boolean, no annotation needed
const [isLoading, setIsLoading] = useState(false);
Les choses changent lorsque la valeur initiale est null ou undefined — dans ce cas, vous devez annoter vous-même le type au lieu de compter sur l’inférence :
interface UserProfile {
id: string;
name: string;
avatarUrl: string;
}
const [user, setUser] = useState<UserProfile | null>(null);
Une bonne habitude à adopter ici est de résister à l’envie de recourir à un type générique simplement pour faire disparaître un message d’erreur. Agir ainsi annule complètement l’avantage que vous cherchiez à obtenir avec TypeScript dès le départ.
Saisie de useEffect : moins compliqué que prévu
Les génériques ne s’appliquent pas vraiment à useEffect, mais il faut néanmoins faire attention aux tableaux de dépendances et à la logique de nettoyage :
useEffect(() => {
const controller = new AbortController();
const fetchUser = async () => {
const res = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
const data: UserProfile = await res.json();
setUser(data);
};
fetchUser();
return () => controller.abort(); // cleanup on unmount
}, [userId]);
Saisie de useReducer pour des états plus complexes
C’est avec ce hook que l’utilité de TypeScript devient évidente :
type CartAction =
| { type: 'ADD_ITEM'; payload: CartItem }
| { type: 'REMOVE_ITEM'; payload: string }
| { type: 'CLEAR_CART' };
function cartReducer(state: CartItem[], action: CartAction): CartItem[] {
switch (action.type) {
case 'ADD_ITEM':
return [...state, action.payload];
case 'REMOVE_ITEM':
return state.filter((item) => item.id !== action.payload);
case 'CLEAR_CART':
return [];
default:
return state;
}
}
Avec les types d’actions modélisés de cette manière, l’envoi d’une action invalide génère une erreur à temps de compilation que vous détectez immédiatement, au lieu d’une surprise en temps de exécution que vous découvrez plus tard.
Rédaction de hooks personnalisés réutilisables avec des génériques
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = window.localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
Comme ce hook est générique, il peut être réutilisé partout dans votre codebase — avec des chaînes de caractères, des objets ou des tableaux — tout en conservant une précision de type totale pour les données que vous lui transmettez.
En bref
- Laissez TypeScript déduire automatiquement l’état simple, mais soyez explicite chaque fois qu’une valeur peut être null ou undefined.
- Modélisez vos actions useReducer sous forme d’union discriminée.
- Utilisez des génériques dans les hooks personnalisés pour qu’ils puissent être réutilisés avec différents types de données.
- Considérez
anycomme un raccourci qui, en réalité, vous coûte plus à long terme que ce qu’il économise.
Choisir entre JavaScript pur et TypeScript
Lorsque vous comprenez l’avantage en termes de sécurité que procurent les hooks typés, une question plus large se pose naturellement : tous les projets devraient-ils vraiment utiliser TypeScript, ou s’agit-il parfois d’une sur-ingénierie ? Pendant longtemps, cela a été présenté comme un choix binaire, et choisir l’un ou l’autre signifiait faire face à des compromis réels.
Auparavant, choisir TypeScript signifiait s’occuper de pipelines de compilation, d’outils tels que ts-node, ainsi que d’une multitude de fichiers de configuration rien que pour faire fonctionner un script. Aujourd’hui, puisque des environnements d’exécution comme Node.js prennent en charge le suppression native des types, ces difficultés ont largement disparu, et la frontière entre l’écriture de JavaScript pur et celle de TypeScript est bien plus fine qu’auparavant.
En examinant l’impact de chaque option sur votre flux de travail quotidien, il devient plus facile de déterminer laquelle convient réellement au projet dont vous avez affaire.
Pourquoi JavaScript pur a encore sa place
JavaScript est le langage que le navigateur et Node.js comprennent nativement. Vous écrivez un fichier, vous le lancez, et il s’exécute immédiatement, sans qu’aucun compilateur ne se mette en travers du chemin et sans avoir besoin de déclarer quoi que ce soit de supplémentaire.
- Prototypage instantané : lorsque vous testez une idée, que vous créez rapidement un script ou que vous développez un petit MVP, JavaScript vous permet d’agir aussi vite que votre esprit peut le concevoir.
- Aucune configuration requise : il n’est pas nécessaire de disposer d’un
tsconfig.jsonni d’une étape de vérification des types simplement pour s’assurer qu’une fonction fait ce à quoi on s’attend. - Moins de charge mentale : votre attention reste concentrée sur la logique réelle du programme plutôt que sur les déclarations de types ou les erreurs du compilateur.
Les inconvénients apparaissent lorsque la base de code JavaScript dépasse quelques milliers de lignes. À ce stade, le refactoring devient risqué — renommer une propriété dans vingt fichiers signifie devoir utiliser une fonction de recherche et remplacement globale en espérant que rien ne cesse de fonctionner correctement lorsque l’application s’exécute.
Pourquoi TypeScript est avantageux à mesure que les projets grandissent
TypeScript ajoute un système de types statiques à JavaScript, agissant comme un correcteur automatique qui signale les problèmes au fur et à mesure que vous tapez — par exemple, en vous avertissant dès que vous tentez de passer une chaîne de caractères dans une fonction qui attend un nombre.
- Documentation toujours précise : les types servent également de documentation dynamique. Une interface indique la forme exacte qu’un objet doit avoir sans que vous ayez besoin de parcourir plusieurs fichiers d’implémentation.
- Réfactoring plus sûr : si vous modifiez un modèle de données ou un schéma de base de données, le compilateur indique tous les endroits dans le codebase qui nécessitent une mise à jour.
- Mieux prise en charge par les éditeurs : des outils tels que VS Code ou Cursor s’appuient sur le serveur de langage de TypeScript pour offrir une autocomplétion rapide et un soulignement des erreurs en temps réel pendant que vous travaillez.
Historiquement, le compromis consistait en une véritable « taxe sur les outils » : la configuration des compilateurs, des cartes de sources et des scripts d’enveloppe ajoutait des étapes supplémentaires à ce qui était autrefois un flux de travail simple.
Pourquoi ce compromis ne s’applique plus de la même manière
La vieille réclamation selon laquelle « l’installation de TypeScript est trop compliquée » n’est plus aussi valable aujourd’hui. Les versions actuelles de Node.js peuvent exécuter directement les fichiers TypeScript, sans étape de compilation séparée, grâce au suppression des types.
Dans l’ombre, le moteur d’exécution supprime simplement vos annotations de type et vos déclarations d’interfaces, laissant un JavaScript pur qui peut être exécuté immédiatement. Cela signifie que vous conservez la sécurité offerte par les types statiques pendant le développement, sans le surcoût d’installation qui allait autrefois de pair.
Adapter l’outil à la tâche
Pour un script à usage unique, une petite tâche d’automatisation ou un projet destiné uniquement à apprendre le fonctionnement du web, JavaScript pur reste la meilleure option. Éviter la structure supplémentaire permet de garder le processus léger et agréable.
Pour presque tout le reste — des bases de code en équipe, des applications avec plusieurs modèles de données interactifs, ou tout projet que vous prévoyez de maintenir plus d’un mois — l’effort minimal requis pour configurer TypeScript en vaut la peine. Étant donné que les environnements d’exécution modernes gèrent désormais l’exécution de manière fluide dans les deux cas, vous n’avez pas vraiment besoin de choisir entre flexibilité et sécurité : la simplicité de JavaScript et les mécanismes de protection de TypeScript sont tous deux disponibles, et le choix dépend principalement de la durée de vie et du niveau de collaboration prévus pour le projet.
Lectures complémentaires
- Les paramètres par défaut modifiés dans TypeScript 6.0 : un guide pratique de migration — Découvrez quels neuf paramètres par défaut du compilateur TypeScript 6.0 ont changé, comment configurer tsconfig pour 2026, et comment préparer des bases de code pour TypeScript 7 basé sur Go.
- Les paramètres par défaut du full-stack JavaScript en 2026 : TypeScript, RSC et au-delà — Explication des raisons pour lesquelles TypeScript, React Server Components et une approche de gestion d’état plus légère sont devenus la stack de production standard pour les équipes JavaScript en 2026.