À propos de ce cours
JavaScript convertit silencieusement les valeurs d’un type à l’autre, ce qui produit des bugs difficiles à repérer, comme '45' + 7 qui donne '457'. Ce chapitre explique la différence entre typage statique et dynamique, fort et faible, et montre comment TypeScript refuse ces mélanges douteux. Vous apprendrez à convertir explicitement vos données, à comprendre le typage structurel et à exploiter toutes les options du mode strict. Vous écrirez aussi des gardes de type pour valider des données externes et des unions discriminées qui garantissent qu’aucun cas n’est oublié. Le chapitre montre enfin pourquoi éviter any, as, ! et @ts-ignore, et comment le typage fort s’applique dans Angular, avant cinq activités pratiques.
-
Objectifs
- Distinguer typage statique / dynamique et typage fort / faible
- Repérer les conversions implicites de JavaScript et les remplacer par des conversions explicites
- Comprendre le typage structurel: quand deux types sont-ils compatibles ?
- Connaître les options du mode strict et les options de rigueur supplémentaires
- Écrire une garde de type personnalisée (
valeur is Type) - Utiliser les unions discriminées et vérifier qu'un
switchtraite tous les cas - Reconnaître et éviter les échappatoires:
any,as,!,@ts-ignore
-
Présentation
- On classe les langages selon deux questions indépendantes:
- Quand les types sont-ils vérifiés ? Avant l'exécution (typage statique) ou pendant (typage dynamique) ;
- Avec quelle rigueur ? Le langage convertit-il silencieusement une valeur d'un type vers un autre (typage faible), ou refuse-t-il de mélanger les types (typage fort) ?
- JavaScript est à la fois dynamique et faible: il ne vérifie rien à l'avance et convertit tout seul, ce qui produit des résultats surprenants.
- TypeScript ajoute une vérification statique et des règles fortes: il refuse les mélanges douteux et oblige à écrire les conversions.
- On classe les langages selon deux questions indépendantes:

- Remarque : « fort » et « faible » ne sont pas des catégories absolues mais une échelle. TypeScript est nettement plus fort que JavaScript, mais il accepte encore certaines conversions (par exemple
'5' + 2, qui est une concaténation légale). Ce chapitre vous apprend à pousser la rigueur au maximum.
Le typage faible de JavaScript
- Quand un opérateur reçoit des valeurs de types différents, JavaScript applique une conversion implicite (ou coercition) : il transforme l'une des valeurs pour que l'opération réussisse, au lieu de signaler une erreur.
console.log('5' * 2); // 10 : '5' converti en nombre
console.log('5' + 2); // '52' : 2 converti en chaîne !
console.log('5' - 2); // 3
console.log(true + 1); // 2 : true vaut 1
console.log([] + {}); // '[object Object]'
console.log(null + 1); // 1 : null vaut 0
console.log(undefined + 1); // NaN
console.log('10' == 10); // true : == convertit avant de comparer
console.log(0 == ''); // true
console.log(Boolean('0'), Boolean(''), Boolean([])); // true false true
console.log('3' > '12', 3 > '12'); // true false
- Le danger : une valeur lue dans un formulaire est toujours une chaîne. Selon l'opérateur utilisé, le calcul est juste (
*,-) ou faux (+), sans aucune alerte.
Ce que TypeScript refuse
- Les mêmes expressions en TypeScript sont presque toutes refusées à la compilation :

console.log('5' * 2);
// error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
console.log(true + 1);
// error TS2365: Operator '+' cannot be applied to types 'boolean' and 'number'.
console.log(null + 1);
// error TS18050: The value 'null' cannot be used here.
const quantite: number = 10;
const saisie: string = '10';
console.log(saisie == quantite);
// error TS2367: This comparison appears to be unintentional because the types 'string' and 'number' have no overlap.
console.log(3 > '12');
// error TS2365: Operator '>' cannot be applied to types 'number' and 'string'.
console.log('5' + 2); // accepté : la concaténation est une opération légale
- Attention :
chaîne + nombrereste autorisé, car c'est une manière courante de construire du texte. C'est le piège le plus fréquent qui échappe à TypeScript : convertissez toujours vos saisies avant de calculer.
Convertir explicitement
- Avec un typage fort, c'est au développeur d'écrire la conversion. On choisit la fonction selon le besoin :
| Conversion | Fonction | Exemple |
|---|---|---|
| chaîne → nombre (strict) | Number(x) |
Number('12.5') → 12.5; Number('12 ans') → NaN |
| chaîne → entier (début de chaîne) | Number.parseInt(x, 10) |
parseInt('42 ans', 10) → 42 |
| chaîne → décimal (début de chaîne) | Number.parseFloat(x) |
parseFloat('15,5') → 15 (la virgule arrête la lecture) |
| nombre → chaîne | String(n), n.toFixed(d) |
(45).toFixed(3) → '45.000' |
| valeur → booléen | Boolean(x), ou mieux une comparaison |
saisie.length > 0 plutôt que Boolean(saisie) |
const saisie: string = '12.5'; // valeur lue dans un champ de formulaire
const note: number = Number(saisie); // 12.5
const entier: number = parseInt('42 ans', 10); // 42 : lit le début de la chaîne
const decimal: number = parseFloat('15,5'); // 15 : la virgule arrête la lecture
const invalide: number = Number('abc'); // NaN
console.log(note + 2, entier, decimal, invalide); // 14.5 42 15 NaN
console.log(Number.isNaN(invalide)); // true : toujours tester NaN
const texte: string = String(2026); // '2026'
const prix: string = (45).toFixed(3); // '45.000'
const actif: boolean = Boolean('non'); // true : toute chaîne non vide est vraie
const vide: boolean = saisie.length > 0; // préférer une comparaison explicite
- Remarque :
NaNest de typenumber. TypeScript ne peut donc pas savoir si une conversion a échoué : testez toujours le résultat avecNumber.isNaN(). Et comparez toujours avec===, jamais avec==.
Le typage structurel
- Pour décider si une valeur convient à un type, TypeScript compare sa forme (ses propriétés et leurs types), et non son nom. C'est le typage structurel, aussi appelé « typage canard » : si ça marche comme un canard et cancane comme un canard, c'est un canard.
- Une valeur est compatible si elle possède au moins toutes les propriétés demandées, avec les bons types.

interface Personne {
nom: string;
email: string;
}
const envoyerMail = (p: Personne): string => `Mail envoyé à ${p.nom} <${p.email}>`;
const ali = { nom: 'Ali', email: 'ali@formaplus.tn', specialite: 'Angular' };
const amira = { nom: 'Amira', email: 'amira@exemple.tn', groupe: 'DEV1' };
console.log(envoyerMail(ali)); // OK : ali possède nom et email (string)
console.log(envoyerMail(amira)); // OK : les propriétés en trop sont tolérées
envoyerMail({ nom: 'Sami', email: 'sami@exemple.tn', groupe: 'DEV2' });
// error TS2353: Object literal may only specify known properties, and 'groupe' does not exist in type 'Personne'.
envoyerMail({ nom: 'Karim' });
// error TS2741: Property 'email' is missing in type '{ nom: string; }' but required in type 'Personne'.
const karim = { nom: 'Karim', email: 2026 };
envoyerMail(karim);
// error TS2345: Argument of type '{ nom: string; email: number; }' is not assignable to parameter of type 'Personne'.
// Types of property 'email' are incompatible.
// Type 'number' is not assignable to type 'string'.
- Pourquoi
amiraest accepté et l'objet de Sami refusé, alors qu'ils ont tous deux une propriétégroupeen trop ?- un objet stocké dans une variable peut servir ailleurs : ses propriétés supplémentaires sont tolérées ;
- un objet écrit directement dans l'appel (ou dans une affectation typée) ne sert qu'ici : une propriété inconnue y est sûrement une faute de frappe. TypeScript applique alors une vérification plus stricte, la vérification des propriétés en excès.
Le mode strict en détail
"strict": truen'est pas une option unique : c'est un raccourci qui active toute une famille de vérifications. Depuis TypeScript 6, il est activé par défaut.

// noImplicitAny : un paramètre doit être typé
function doubler(x) { return x * 2; }
// error TS7006: Parameter 'x' implicitly has an 'any' type.
// strictNullChecks : find() peut ne rien trouver
const titres: string[] = ['Angular', 'Flutter'];
const trouve = titres.find((t) => t.startsWith('R'));
console.log(trouve.length);
// error TS18048: 'trouve' is possibly 'undefined'.
// strictPropertyInitialization : une propriété de classe doit être initialisée
class Panier {
proprietaire: string;
articles: string[] = [];
}
// error TS2564: Property 'proprietaire' has no initializer and is not definitely assigned in the constructor.
// useUnknownInCatchVariables : on ne sait pas ce qui a été lancé
try {
JSON.parse('{ invalide');
} catch (erreur) {
console.log(erreur.message);
// error TS18046: 'erreur' is of type 'unknown'.
}
- Pour l'erreur attrapée par
catch, on vérifie son type avant de l'utiliser :if (erreur instanceof Error) console.log(erreur.message); - Options supplémentaires: elles ne font pas partie de
strictet s'ajoutent une à une danstsconfig.json. Angular en active plusieurs dans tout nouveau projet.
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true
}
}
// noUncheckedIndexedAccess : une case de tableau peut ne pas exister
const notes: number[] = [12, 15];
const troisieme = notes[2]; // number | undefined
console.log(troisieme.toFixed(1));
// error TS18048: 'troisieme' is possibly 'undefined'.
const coefficients: Record<string, number> = { Angular: 3 };
console.log(coefficients['Flutter'] * 2);
// error TS2532: Object is possibly 'undefined'.
// noImplicitReturns : toutes les branches doivent renvoyer une valeur
function mention(moyenne: number) {
if (moyenne >= 16) return 'Très bien';
else if (moyenne >= 10) return 'Admis';
}
// error TS7030: Not all code paths return a value.
// noFallthroughCasesInSwitch : pas de case qui « tombe » dans le suivant
function couleur(statut: 'ok' | 'erreur'): string {
let c = '';
switch (statut) {
case 'ok':
c = 'success'; // break oublié !
case 'erreur':
c = 'danger';
}
return c;
}
// error TS7029: Fallthrough case in switch.
// exactOptionalPropertyTypes : « optionnel » veut dire « absent », pas « undefined »
interface Options { theme?: 'clair' | 'sombre' }
const options: Options = { theme: undefined };
// error TS2375: Type '{ theme: undefined; }' is not assignable to type 'Options' with 'exactOptionalPropertyTypes: true'. …
- Remarque :
noUncheckedIndexedAccessest l'option qui attrape le plus de bugs réels (index hors limites, clé absente d'un dictionnaire), mais elle oblige à écrire?.ou??à chaque accès. Elle est recommandée partsc --init.
Les gardes de type personnalisées
- Les tests
typeof,inouArray.isArrayrétrécissent un type, mais seulement à l'endroit où ils sont écrits. Pour réutiliser une vérification, on l'enferme dans une fonction dont le type de retour estvaleur is Type: c'est une garde de type (type guard). - Quand la garde renvoie
true, TypeScript considère la valeur comme étant du type annoncé. C'est l'outil idéal pour valider les données d'une API.
interface Stagiaire {
nom: string;
moyenne: number;
}
// Garde de type : renvoie un boolean, et informe TypeScript du type
function estStagiaire(valeur: unknown): valeur is Stagiaire {
return (
typeof valeur === 'object' &&
valeur !== null &&
'nom' in valeur &&
typeof valeur.nom === 'string' &&
'moyenne' in valeur &&
typeof valeur.moyenne === 'number'
);
}
const donnees: unknown = JSON.parse('[{"nom":"Amira","moyenne":15.5},{"nom":"Sami"},42]');
if (Array.isArray(donnees)) {
const stagiaires = donnees.filter(estStagiaire); // Stagiaire[]
for (const s of stagiaires) {
console.log(`${s.nom} : ${s.moyenne.toFixed(2)}`); // Amira : 15.50
}
console.log(`${donnees.length - stagiaires.length} élément(s) ignoré(s)`); // 2 élément(s) ignoré(s)
}
- Attention : TypeScript fait confiance au corps de la garde. Si elle renvoie
truesans tout vérifier, le typage devient faux. Une garde doit tester chaque propriété utilisée ensuite.
Les unions discriminées et l'exhaustivité
- Une union discriminée est une union d'objets qui partagent une propriété dont la valeur est un type littéral différent pour chaque forme (ici
mode). Tester cette propriété suffit à TypeScript pour savoir quelle forme est présente. - Dans le
default, toutes les formes ont été traitées : la valeur est de typenever. L'affecter à une variableneverest une vérification d'exhaustivité: si l'on ajoute une forme sans la traiter, la compilation échoue.

type Paiement =
| { mode: 'especes'; montant: number }
| { mode: 'carte'; montant: number; numero: string }
| { mode: 'virement'; montant: number; iban: string };
function recu(p: Paiement): string {
switch (p.mode) {
case 'especes':
return `${p.montant.toFixed(3)} DT en espèces`;
case 'carte':
return `${p.montant.toFixed(3)} DT par carte **** ${p.numero.slice(-4)}`; // p.numero existe ici
case 'virement':
return `${p.montant.toFixed(3)} DT par virement (${p.iban})`;
default: {
const impossible: never = p; // vérification d'exhaustivité
return impossible;
}
}
}
console.log(recu({ mode: 'especes', montant: 250 }));
// 250.000 DT en espèces
console.log(recu({ mode: 'carte', montant: 480, numero: '4970101234567890' }));
// 480.000 DT par carte **** 7890
console.log(recu({ mode: 'virement', montant: 320, iban: 'TN59 1000 6035' }));
// 320.000 DT par virement (TN59 1000 6035)
- Si l'on ajoute
| { mode: 'cheque'; montant: number; numero: string }au type sans modifierrecu:
error TS2322: Type '{ mode: "cheque"; montant: number; numero: string; }' is not assignable to type 'never'.
- Sans le
switch, accéder àp.numeroest refusé, car cette propriété n'existe pas dans toutes les formes de l'union (error TS2339).
Les échappatoires à éviter
- TypeScript offre des moyens de désactiver ses vérifications. Chacun est une promesse que le compilateur ne contrôle pas : si elle est fausse, l'erreur revient à l'exécution.

const titres: string[] = ['Angular', 'Flutter'];
// 1. any : la contagion
const brut: any = JSON.parse('{"prix": "45"}');
const prix: number = brut.prix; // accepté… mais prix contient la chaîne '45'
console.log(prix + 10); // 4510 : concaténation, pas addition !
// 2. as : une affirmation, pas une conversion
const valeur = '45' as unknown as number; // double assertion : tsc ne dit plus rien
console.log(valeur.toFixed(2)); // TypeError à l'exécution
// 3. ! : « ce n'est pas undefined, crois-moi »
const premierR = titres.find((t) => t.startsWith('R'))!;
console.log(premierR.length); // TypeError à l'exécution
// 4. @ts-ignore : fait taire la ligne suivante, quelle que soit l'erreur
// @ts-ignore
const total: number = 'cent';
// 5. @ts-expect-error : comme @ts-ignore, mais signale quand l'erreur a disparu
// @ts-expect-error
const quantite: number = 3;
// error TS2578: Unused '@ts-expect-error' directive.
- L'exemple de
anyest le plus traître :prixest annoténumber, mais contient une chaîne. Le typage fort est rompu sans aucun message, et le bug apparaît plus loin. - Les alternatives sûres :
- à la place de
any:unknown+ une garde de type ; - à la place de
as: une vraie conversion (Number()) ou une garde ; pour vérifier un objet,satisfies; - à la place de
!: un test,?.ou??; - à la place de
@ts-ignore: corriger le code, ou en dernier recours@ts-expect-erroraccompagné d'un commentaire.
- à la place de
Le typage fort dans Angular
- Un projet créé avec
ng newest strict par défaut. Voici letsconfig.jsongénéré par Angular 22 (extrait) :strictn'y figure plus car il est actif par défaut, et quatre options de rigueur sont ajoutées.
{
"compilerOptions": {
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true
}
}
- Les templates sont eux aussi vérifiés en mode strict : une propriété mal orthographiée dans
{{ ... }}ou un mauvais type passé à un composant bloque la compilation. - Les formulaires réactifs sont typés :
new FormControl('')est de typeFormControl<string | null>(la valeur redevientnullaprès unreset()), tandis que{ nonNullable: true }donne unFormControl<string>.
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
interface Inscription {
nom: string;
email: string;
formation: 'angular' | 'flutter';
}
@Component({
selector: 'app-inscription',
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="formulaire" (ngSubmit)="envoyer()">
<input formControlName="nom" />
<input formControlName="email" type="email" />
<select formControlName="formation">
<option value="angular">Angular</option>
<option value="flutter">Flutter</option>
</select>
<button [disabled]="formulaire.invalid">S'inscrire</button>
</form>
`,
})
export class InscriptionForm {
private http = inject(HttpClient);
formulaire = new FormGroup({
nom: new FormControl('', { nonNullable: true, validators: Validators.required }),
email: new FormControl('', { nonNullable: true, validators: Validators.email }),
formation: new FormControl<Inscription['formation']>('angular', { nonNullable: true }),
});
envoyer(): void {
const donnees: Inscription = this.formulaire.getRawValue(); // vérifié par TypeScript
this.http.post<{ id: number }>('/api/inscriptions', donnees)
.subscribe((reponse) => console.log(`Inscription n° ${reponse.id}`));
}
}
- Avec les contrôles non typés ou
nullable, l'affectationconst donnees: Inscription = ...serait refusée (Type 'string | null' is not assignable to type 'string') : le typage fort vous oblige à traiter le cas d'un champ vide. - Attention :
http.post<{ id: number }>ouhttp.get<Formation[]>ne vérifient pas la réponse du serveur : c'est une affirmation, commeas. Pour des données dont vous n'êtes pas sûr, validez-les avec une garde de type.
Activité 1 : du typage faible au typage fort
- Le programme JavaScript suivant calcule le total d'une commande à partir de valeurs saisies dans un formulaire. L'exécuter avec Node et noter le résultat.
const prixSaisi = '45.500';
const quantiteSaisie = '2';
const remiseSaisie = '';
const fraisLivraison = 7;
const sousTotal = prixSaisi * quantiteSaisie;
const remise = sousTotal * remiseSaisie / 100;
const total = sousTotal - remise + fraisLivraison;
const affichage = prixSaisi + fraisLivraison;
console.log(sousTotal, remise, total, affichage);
console.log(quantiteSaisie == 2, remiseSaisie == 0);
// Node affiche :
// 91 0 98 45.5007
// true true
- Copier ce code dans
src/11-panier.tset compiler : TypeScript signale 5 erreurs. Noter leur code et leur ligne. - Une ligne contient pourtant un bug que TypeScript n'a pas signalé : laquelle, et pourquoi ?
- Corriger le programme avec des conversions explicites (
Number,Number.parseInt), des variables annotées et===, pour obtenir :
91 0 98 52.5
true true
-
Activité 2 : compatible ou pas ?
- Dans
src/12-etiquettes.ts, recopier le code suivant. Avant de compiler, indiquer en commentaire pour chaque ligne marquée A à F : « accepté » ou « refusé », avec la raison.
- Dans
interface Produit {
nom: string;
prix: number;
}
const etiquette = (p: Produit): string => `${p.nom.padEnd(10, '.')} ${p.prix.toFixed(3)} DT`;
const clavier = { nom: 'Clavier', prix: 45, stock: 12 };
const souris = { nom: 'Souris', prix: '20' };
const ecran = { nom: 'Écran', prix: 420, garantie: '2 ans' } satisfies Produit;
console.log(etiquette(clavier)); // A
console.log(etiquette(souris)); // B
console.log(etiquette({ nom: 'Tapis', prix: 8.5 })); // C
console.log(etiquette({ nom: 'Casque', prix: 89, couleur: 'noir' })); // D
console.log(etiquette(ecran)); // E
console.log(etiquette({ nom: 'Câble' })); // F
- Compiler et comparer avec vos prévisions. Attention au cas E : à quelle ligne l'erreur est-elle signalée ?
- Corriger les lignes refusées pour que tout le fichier compile, sans utiliser
asniany.
Activité 3 : plus strict que strict
- Créer
src/13-planning.tsavec le code ci-dessous et compiler : avec la configuration du cours, aucune erreur.
type Jour = 'lundi' | 'mardi' | 'mercredi' | 'jeudi' | 'vendredi';
const salles: string[] = ['B12', 'B14', 'Labo 1'];
const formateurs: Record<string, string> = { Angular: 'Ali', Flutter: 'Mouna' };
function salleDuJour(numero: number): string {
return salles[numero].toUpperCase();
}
function formateurDe(module: string): string {
return formateurs[module].toUpperCase();
}
function dureeSeance(jour: Jour) {
switch (jour) {
case 'lundi':
case 'mardi':
return 3;
case 'mercredi':
console.log('Demi-journée');
case 'jeudi':
return 4;
case 'vendredi':
if (Math.random() > 2) return 2;
}
}
console.log(salleDuJour(0), salleDuJour(3));
console.log(formateurDe('Angular'), formateurDe('TypeScript'));
console.log(dureeSeance('mercredi'), dureeSeance('vendredi'));
- Exécuter
node src/13-planning.ts: que se passe-t-il ? - Ajouter dans
tsconfig.jsonles optionsnoUncheckedIndexedAccess,noImplicitReturnsetnoFallthroughCasesInSwitch, puis recompiler : 4 erreurs apparaissent. Les relier aux bugs du programme. - Corriger le code (sans désactiver les options) : une salle inexistante affiche
NON ATTRIBUÉE, un module sans formateur afficheÀ DÉFINIR, le mercredi et le vendredi durent 2 heures, etdureeSeancedéclare son type de retour.
B12 NON ATTRIBUÉE
ALI À DÉFINIR
2 2
- Pourquoi
case 'lundi':suivi directement decase 'mardi':n'est-il pas signalé parnoFallthroughCasesInSwitch?
Activité 4 : valider les données d'une API
- Dans
src/14-catalogue-api.ts, déclarer le typeNiveau('debutant' | 'intermediaire' | 'avance') et l'interfaceFormation(titre: string,niveau: Niveau,places: number). - Écrire deux gardes de type :
estNiveau(valeur: unknown): valeur is NiveauetestFormation(valeur: unknown): valeur is Formation(le nombre de places doit être positif ou nul). - Analyser la réponse ci-dessous dans une variable
unknown, garder uniquement les formations valides avecfilter, et afficher :
const reponseApi = `[
{ "titre": "Angular", "niveau": "intermediaire", "places": 12 },
{ "titre": "Flutter", "niveau": "expert", "places": 8 },
{ "titre": "TypeScript", "niveau": "debutant", "places": "dix" },
{ "titre": "Node.js", "niveau": "avance", "places": 5 },
null
]`;
// Résultat attendu :
// Angular (intermediaire) : 12 place(s)
// Node.js (avance) : 5 place(s)
// 2 formation(s) valide(s), 3 rejetée(s)
- Remplacer temporairement la validation par
JSON.parse(reponseApi) as Formation[]: combien de formations sont affichées, et que provoque l'élémentnull?
Activité 5 : une union discriminée exhaustive
- Dans
src/15-alertes.ts, déclarer le typeAlerte, union discriminée par la propriétécanal:'email'avecdestinataireetsujet;'sms'avectelephoneettexte;'push'avecappareil,titreeturgent(booléen).
- Écrire
verifierExhaustivite(valeur: never): neverqui lance une erreur, puisdecrire(a: Alerte): stringavec unswitchqui appelleverifierExhaustivitedans ledefault.
const envois: Alerte[] = [
{ canal: 'email', destinataire: 'amira@exemple.tn', sujet: 'Convocation' },
{ canal: 'sms', telephone: '22 123 456', texte: 'Cours annulé demain' },
{ canal: 'push', appareil: 'Android', titre: 'Nouvelle note', urgent: true },
];
envois.forEach((a) => console.log(decrire(a)));
// Résultat attendu :
// Email à amira@exemple.tn – « Convocation »
// SMS au 22 123 456 (19 caractères)
// Push sur Android [URGENT] – Nouvelle note
- Ajouter au type le canal
'whatsapp'(avectelephoneettexte) sans modifierdecrire. Noter l'erreur, puis traiter ce nouveau cas. - Faire un commit après chaque activité, puis un push :
git commit -m "Typage fort : activité N".
Erreurs fréquentes
| Message ou symptôme | Cause | Solution |
|---|---|---|
error TS2362 / TS2363: The left-hand (right-hand) side of an arithmetic operation must be of type … 'number' … |
Calcul avec une chaîne (souvent une saisie de formulaire) | Convertir d'abord : Number(saisie) |
error TS2367: This comparison appears to be unintentional because the types 'string' and 'number' have no overlap. |
Comparaison de deux types différents | Convertir l'une des valeurs, et comparer avec === |
Résultat du type '4510' au lieu de 55, sans erreur |
chaîne + nombre: concaténation acceptée par TypeScript |
Convertir les saisies avant tout calcul ; bannir any |
NaN affiché alors que le code compile |
Conversion d'une chaîne invalide (NaN est un number) |
Tester avec Number.isNaN() après chaque conversion |
error TS2353: Object literal may only specify known properties… |
Propriété en trop (souvent une faute de frappe) dans un objet écrit directement | Corriger le nom, ou ajouter la propriété au type |
error TS2741: Property 'x' is missing in type … |
Propriété obligatoire absente | Fournir la propriété, ou la rendre optionnelle dans le type |
error TS2564: Property 'x' has no initializer… |
Propriété de classe jamais initialisée | L'initialiser à la déclaration ou dans le constructeur |
error TS18046: 'erreur' is of type 'unknown'. dans un catch |
On ne sait pas ce qui a été lancé | if (erreur instanceof Error) { … erreur.message … } |
error TS2532: Object is possibly 'undefined'. sur t[i] |
noUncheckedIndexedAccess: la case peut ne pas exister |
t[i]?.… ?? valeurParDefaut, ou tester la valeur |
error TS7030: Not all code paths return a value. |
Une branche ne renvoie rien | Ajouter le return manquant et annoter le type de retour |
error TS7029: Fallthrough case in switch. |
break ou return oublié dans un case |
Terminer chaque case non vide par break ou return |
Type '…' is not assignable to type 'never'. |
Un cas d'une union discriminée n'est pas traité | Ajouter le case manquant |
error TS2578: Unused '@ts-expect-error' directive. |
L'erreur attendue n'existe plus | Supprimer la directive |
TypeError à l'exécution alors que tout compile |
any, as, ! ou @ts-ignore ont désactivé la vérification |
Remplacer par unknown, une garde de type, ?. / ?? |
-
À retenir
- Statique / dynamique: quand le type est vérifié ; fort / faible: si le langage convertit tout seul. JavaScript est dynamique et faible ; TypeScript ajoute une vérification statique et des règles fortes.
- TypeScript refuse les calculs et comparaisons entre types différents, mais accepte
chaîne + nombre: convertissez explicitement avecNumber(),String(), et testezNaN. - Le typage structurel compare les formes : un objet compatible a au moins les propriétés demandées. Les objets écrits directement ne doivent pas avoir de propriétés inconnues.
"strict": true(par défaut) regroupenoImplicitAny,strictNullChecks… ;noUncheckedIndexedAccess,noImplicitReturns,noFallthroughCasesInSwitchrenforcent encore la rigueur.- Une garde de type
valeur is Tvalide une donnéeunknown; une union discriminée +nevergarantit qu'aucun cas n'est oublié. any,as,!et@ts-ignorerompent le typage fort : à remplacer par des vérifications réelles.
Le typage fort ne se décrète pas, il se protège : chaque conversion écrite, chaque donnée vérifiée et chaque any évité garde à TypeScript le pouvoir de trouver vos bugs à votre place.