TypeScript
Tous les projets de cette stack sont écrits en TypeScript (le --typescript de create-next-app, les fichiers .tsx). Ce chapitre t'apprend à le lire et à l'écrire, en partant de zéro.
.ts/.tsx d'un projet créé avec un des guides de ce site.
Une seule rĂšgle Ă retenir avant de commencer : TypeScript, c'est du JavaScript auquel on ajoute des annotations de type. Si tu enlevais toutes les annotations (
: string, : number...), le code redeviendrait du JavaScript classique et fonctionnerait pareil. TypeScript ne change rien Ă l'exĂ©cution â il vĂ©rifie juste, avant que ça tourne, que tu ne te trompes pas de type quelque part.
Pourquoi TypeScript ?
Comprendre le problÚme que ça résout, avant d'apprendre la syntaxe.
-
Regarde ce qui plante en JavaScript pur
// JavaScript classique â ce code est acceptĂ© sans broncher function calculerPrix(prix, quantite) { return prix * quantite; } calculerPrix("dix", 3); // "dix" * 3 â NaN, et personne n'est prĂ©venu
En JavaScript, rien ne t'empĂȘche d'appelercalculerPrixavec une chaĂźne de caractĂšres Ă la place d'un nombre. L'erreur (NaN, « Not a Number ») n'apparaĂźt que quand le code tourne â parfois chez un vrai utilisateur, des heures aprĂšs que tu as Ă©crit la fonction. -
Le mĂȘme code, en TypeScript
// TypeScript â les ": type" sont les annotations function calculerPrix(prix: number, quantite: number) { return prix * quantite; } calculerPrix("dix", 3); // erreur IMMĂDIATE, avant mĂȘme d'exĂ©cuter le code
Le: numberaprĂšs chaque paramĂštre dit Ă TypeScript « ceci doit toujours ĂȘtre un nombre ». Si tu te trompes, ton Ă©diteur (VS Code, etc.) souligne la ligne en rouge tout de suite â tu corriges avant mĂȘme d'enregistrer, pas aprĂšs un bug en production.đ Essaie toi-mĂȘme : colle les deux blocs sur le Playground. Le premier (JS) ne montre aucune erreur. Le second souligne"dix"en rouge avec le messageArgument of type 'string' is not assignable to parameter of type 'number'. -
Retiens l'idĂ©e en une phraseTypeScript n'exĂ©cute jamais de code diffĂ©remment de JavaScript â il se contente de vĂ©rifier les types avant l'exĂ©cution et de refuser de compiler si tu t'es trompĂ©. C'est un correcteur orthographique pour ton code, pas un nouveau langage Ă part entiĂšre.
Types de base
Les quatre types que tu utiliseras dans 90% de ton code.
Prérequis : étape 1 lue.
-
Texte, nombre, booléen
let nom: string = "Samba"; let age: number = 32; let estConnecte: boolean = true;
La syntaxe est toujours la mĂȘme :nom-de-variable: type = valeur. Trois types couvrent la grande majoritĂ© des cas :string(texte, entre guillemets),number(tout nombre, entier ou dĂ©cimal â pas de type sĂ©parĂ© pour les dĂ©cimaux),boolean(seulementtrueoufalse).đ Essaie toi-mĂȘme : Ă©crisage = "trente-deux";juste en dessous. TypeScript souligne la ligne :Type 'string' is not assignable to type 'number'. -
Un tableau
let notes: number[] = [12, 15, 8, 17]; let prenoms: string[] = ["Awa", "Moussa", "Fatou"];
type[]veut dire « un tableau qui ne contient que ce type ».notes.push("bien")serait refusĂ© â un tableau denumber[]ne peut accueillir que des nombres. -
Laisse TypeScript deviner tout seul, quand c'est évident
let ville = "Paris"; // pas besoin d'Ă©crire ": string", TypeScript le devine ville = 75; // erreur quand mĂȘme â le type est "verrouillĂ©" dĂšs l'affectation
C'est l'infĂ©rence de type : si tu donnes une valeur dĂšs la dĂ©claration, TypeScript comprend le type tout seul. Tu n'as besoin d'Ă©crire: stringexplicitement que quand la valeur n'est pas encore connue (ex. un paramĂštre de fonction, vu au chapitre suivant).đ Essaie toi-mĂȘme : survolevilleavec ta souris dans VS Code (ou le Playground) â une infobulle affichelet ville: string, mĂȘme si tu ne l'as jamais Ă©crit. -
Le piÚge à éviter :
anylet donnee: any = "texte"; donnee = 42; // acceptĂ© donnee = true; // acceptĂ© aussi â plus aucune vĂ©rification
anydit Ă TypeScript « n'importe quel type, arrĂȘte de vĂ©rifier ». Ăa compile toujours, mais tu perds tout l'intĂ©rĂȘt de TypeScript. Ăviteanyâ si tu ne connais pas encore le type exact, prĂ©fĂšreunknown(mĂȘme idĂ©e, mais TypeScript t'oblige Ă vĂ©rifier le type avant de t'en servir).
Typer une fonction
Sécuriser ce qui entre (les paramÚtres) et ce qui sort (la valeur retournée) d'une fonction.
Prérequis : étape 2 validée.
-
Type des paramĂštres et du retour
function additionner(a: number, b: number): number { return a + b; }Chaque paramĂštre est typĂ© individuellement (a: number, b: number). Le: numberjuste avant l'accolade{type ce que la fonctionreturn. Si le corps de la fonction retourne autre chose qu'unnumber, TypeScript refuse.đ Essaie toi-mĂȘme : remplacereturn a + b;parreturn "rĂ©sultat: " + (a + b);â TypeScript souligne cette ligne, car une chaĂźne ne correspond pas au: numberpromis. -
Une fonction qui ne retourne rien
function afficherMessage(texte: string): void { console.log(texte); }voidveut dire « cette fonction ne retourne aucune valeur exploitable » â utilisĂ© pour les fonctions qui font une action (afficher, envoyer un email...) sansreturn. -
Fonction fléchée (la forme la plus courante en React)
const multiplier = (a: number, b: number): number => a * b;
MĂȘme principe, Ă©criture diffĂ©rente â c'est la syntaxe que tu croiseras le plus dans les composants React de tes guides (ex.const handleClick = () => { ... }).đ Essaie toi-mĂȘme : appellemultiplier(4, "deux")â l'erreur pointe directement le deuxiĂšme argument, avant mĂȘme que la fonction s'exĂ©cute.
Objets & interfaces
DĂ©crire la forme exacte d'un objet â la brique la plus utilisĂ©e dans un vrai projet (un utilisateur, un produit, une commande...).
Prérequis : étape 3 validée.
-
Type inline sur un objet
let utilisateur: { nom: string; age: number } = { nom: "Aziz", age: 32, };Fonctionne, mais devient illisible dĂšs que l'objet grossit ou que tu dois le rĂ©utiliser ailleurs (dans une autre fonction, un autre fichier). D'oĂč l'interfaceci-dessous. -
La mĂȘme chose avec une
interfaceâ la façon propre de faireinterface Utilisateur { nom: string; age: number; } const utilisateur: Utilisateur = { nom: "Aziz", age: 32, };Uneinterfacenomme une forme d'objet une seule fois, rĂ©utilisable partout (function afficher(u: Utilisateur) { ... }). C'est l'Ă©quivalent TypeScript d'un modĂšle Prisma ou d'un schĂ©ma de base de donnĂ©es, mais cĂŽtĂ© code.đ Essaie toi-mĂȘme : retireage: 32,de l'objet â TypeScript refuse avecProperty 'age' is missing. Ajoute une propriĂ©tĂ©ville: "Paris"non prĂ©vue par l'interface â refusĂ© aussi (Object literal may only specify known properties). -
Réutilise l'interface dans une fonction
function saluer(u: Utilisateur): string { return `Bonjour ${u.nom}, tu as ${u.age} ans.`; }C'est exactement ce que tu Ă©criras pour typer lespropsd'un composant React (Ă©tape 8) ou le retour d'une requĂȘte Prisma (prisma.utilisateur.findMany()renvoie un tableau de ce type).
Union & types littéraux
Limiter une valeur Ă un ensemble prĂ©cis de possibilitĂ©s â trĂšs utilisĂ© pour un statut, un rĂŽle, une catĂ©gorie.
Prérequis : étape 4 validée.
-
Une variable qui accepte plusieurs types
let identifiant: string | number; identifiant = "abc123"; // accepté identifiant = 42; // accepté aussi identifiant = true; // refusé
Le|(« ou ») crĂ©e une union : la variable peut ĂȘtre l'un OU l'autre des types listĂ©s, jamais un troisiĂšme. -
Le cas le plus utile : limiter à des valeurs exactes (types littéraux)
let statut: "en_attente" | "payee" | "annulee"; statut = "payee"; // acceptĂ© statut = "en cours"; // refusĂ© â cette chaĂźne exacte n'est pas dans la liste
Au lieu d'unstringgĂ©nĂ©rique (qui accepterait n'importe quelle faute de frappe comme"payĂ©"ou"Payee"), tu listes prĂ©cisĂ©ment les seules valeurs valides. C'est exactement ce que fait unenumPrisma dansschema.prisma, cĂŽtĂ© TypeScript.đ Essaie toi-mĂȘme : Ă©crisstatut = "Payee";(majuscule) â refusĂ©, car ce n'est pas exactement une des trois chaĂźnes autorisĂ©es. -
Combiné dans une interface
interface Commande { id: string; statut: "en_attente" | "payee" | "annulee"; }C'est la mĂȘme logique que la Server ActioncreateProduitoucreateQueuede tes autres guides â le champstatutne peut jamais contenir une valeur inattendue, ni dans le code, ni via une faute de frappe.
Optionnel, null & undefined
GĂ©rer proprement les valeurs qui peuvent manquer â la source n°1 de bugs en JavaScript (« Cannot read property of undefined »).
Prérequis : étape 5 validée.
-
Une propriété facultative
interface Produit { nom: string; description?: string; // le "?" veut dire : peut ĂȘtre absente } const p1: Produit = { nom: "Chaise" }; // acceptĂ©, pas de description const p2: Produit = { nom: "Table", description: "En bois massif" }; // acceptĂ© aussiSans le?, TypeScript exigeraitdescriptionsur chaque objet. Avec, c'est facultatif â mais TypeScript te force alors Ă vĂ©rifier qu'elle existe avant de l'utiliser (Ă©tape suivante). -
Vérifier avant d'utiliser une valeur optionnelle
function afficherDescription(p: Produit) { console.log(p.description.toUpperCase()); // erreur : peut ĂȘtre undefined } function afficherDescriptionCorrige(p: Produit) { if (p.description) { console.log(p.description.toUpperCase()); // acceptĂ© : TS sait qu'elle existe ici } }TypeScript refuse d'appeler une mĂ©thode (.toUpperCase()) sur une valeur qui pourrait ĂȘtreundefinedâ c'est exactement le bug JavaScript classiqueCannot read properties of undefined, mais dĂ©tectĂ© avant l'exĂ©cution plutĂŽt que chez l'utilisateur.đ Essaie toi-mĂȘme : colle la premiĂšre fonction (sans leif) â TypeScript soulignep.descriptionavec'p.description' is possibly 'undefined'. -
Le raccourci
?.(optional chaining)console.log(p.description?.toUpperCase()); // si "description" n'existe pas, retourne undefined au lieu de planterĂquivalent plus court duifprĂ©cĂ©dent, trĂšs courant dans le code React de tes guides (ex.session?.user.idpour l'authentification, dĂ©jĂ vu dans les guides backoffice/e-commerce/SaaS).
Generics â les bases
Ăcrire une fonction qui marche avec n'importe quel type, sans perdre la sĂ©curitĂ© des types. La notion la plus abstraite du cours â vas-y doucement.
Prérequis : étape 6 validée.
-
Le problÚme que les generics résolvent
function premierElement(tableau: any[]) { return tableau[0]; } const x = premierElement([1, 2, 3]); // x est "any" â TS a perdu la trace du type const y = premierElement(["a", "b", "c"]); // y est "any" aussi, alors que c'est forcĂ©ment une stringAvecany[], la fonction accepte tout, mais TypeScript ne peut plus te dire ce quexouycontiennent rĂ©ellement â tu as perdu toute la sĂ©curitĂ© de typage Ă l'intĂ©rieur de la fonction. -
La mĂȘme fonction, avec un generic
<T>function premierElement<T>(tableau: T[]): T { return tableau[0]; } const x = premierElement([1, 2, 3]); // x est bien typĂ© "number" const y = premierElement(["a", "b", "c"]); // y est bien typĂ© "string"Test un « type variable » : un espace rĂ©servĂ© que TypeScript remplace automatiquement par le vrai type Ă chaque appel ânumberpour le premier appel,stringpour le second. Tu peux l'appelerT(convention) ou n'importe quel nom, comme un paramĂštre normal mais pour un type.đ Essaie toi-mĂȘme : survolexpuisydans VS Code/Playground â les infobulles affichentconst x: numberetconst y: string, TypeScript a devinĂ© tout seul. -
LĂ oĂč tu en verras le plus souvent :
useStateen Reactconst [age, setAge] = useState<number>(0); // setAge("trente") serait refusĂ© : age doit toujours rester un "number"Tu n'as pas besoin d'Ă©crire tes propres generics au quotidien â mais tu vas constamment en utiliser (useState<T>,Array<T>, les hooks de tes composants React) donc reconnaĂźtre la syntaxe<...>et savoir qu'elle « fixe » un type suffit largement pour l'instant.
TypeScript avec React
Tout assembler sur un composant réel, comme ceux que tu écris dans les guides vitrine/backoffice/e-commerce/SaaS/mobile.
Prérequis : étapes 1 à 7 validées.
-
Typer les
propsd'un composant// components/carte-produit.tsx interface CarteProduitProps { nom: string; prix: number; enPromo?: boolean; // optionnel, vu Ă l'Ă©tape 6 } export default function CarteProduit({ nom, prix, enPromo }: CarteProduitProps) { return ( <div> <h3>{nom}</h3> <p>{prix} âŹ{enPromo ? " â en promo" : ""}</p> </div> ); }
C'est l'interface de l'Ă©tape 4, appliquĂ©e auxpropsd'un composant. Si quelqu'un (toi, dans trois mois) utilise<CarteProduit nom="Chaise" />sansprix, TypeScript refuse â impossible d'oublier une prop obligatoire, contrairement au JavaScript classique.đ Essaie toi-mĂȘme : dans un fichier qui importe ce composant, Ă©cris<CarteProduit nom="Chaise" prix="20" />(avec des guillemets autour de 20) â erreur immĂ©diate,prixattend unnumber, pas unestring. -
Typer un état avec
useState"use client"; import { useState } from "react"; export default function Compteur() { const [total, setTotal] = useState<number>(0); return ( <button onClick={() => setTotal(total + 1)}> CliquĂ© {total} fois </button> ); }Le generic (Ă©tape 7) suruseState<number>garantit quetotalreste un nombre pendant toute la vie du composant âsetTotal("beaucoup")serait refusĂ©. -
Typer les données qui viennent de la base (Prisma)
import { prisma } from "@/lib/db/client"; export default async function ListeProduits() { const produits = await prisma.produit.findMany(); // "produits" est dĂ©jĂ typĂ© automatiquement par Prisma â pas d'interface Ă Ă©crire Ă la main return ( <ul> {produits.map((produit) => ( <li key={produit.id}>{produit.nom}</li> ))} </ul> ); }Bonne nouvelle pour la suite : Prisma gĂ©nĂšre lui-mĂȘme les types TypeScript Ă partir de tonschema.prisma(vu dans les guides backoffice/e-commerce/SaaS). Tu n'as pas besoin de réécrire une interface pour chaque modĂšle âproduit.nomest dĂ©jĂ typĂ©string,produit.prixdĂ©jĂ typĂ©number, automatiquement.