Séance 1

Premiers pas en TypeScript

Rémi Sharrock

TypeScript 101, Télécom Paris

Organisation de l’année

Séance Date Horaire Salle
1 : Premiers pas mar. 09/02/2027 8 h 30 à 11 h 45 à compléter
2 : Fonctions, objets et tableaux ven. 12/02/2027 8 h 30 à 11 h 45 à compléter
3 : Programmation orientée objet mar. 23/02/2027 8 h 30 à 11 h 45 à compléter
4 : Évaluation 1 ven. 05/03/2027 8 h 30 à 11 h 45 à compléter
5 : Données et erreurs mar. 09/03/2027 8 h 30 à 11 h 45 à compléter
6 : Asynchrone et réseau mar. 16/03/2027 8 h 30 à 11 h 45 à compléter
7 : Applications complètes ven. 26/03/2027 15 h 30 à 17 h à compléter
8 : Évaluation 2 jeu. 29/04/2027 8 h 30 à 11 h 30 à compléter

Pause de 10 h à 10 h 15 pendant les séances du matin.

Encadrants : Rémi Sharrock, second encadrant à compléter.

Comment se passe l’UE

  • Chaque séance : un mini-cours de 30 à 45 minutes, puis des exercices sur le site
  • Les exercices portent un niveau : essentiel, entraînement, approfondissement. L’étiquette transfert marque ceux qui préparent le mieux aux évaluations
  • Deux évaluations d’environ 3 h, sans accès à internet et sans IA, avec l’outillage d’examen fourni
  • La note de l’évaluation 1 et un corrigé commenté sont rendus avant l’évaluation 2

Pas de projet ni de rendu noté : la note finale est la moyenne des deux évaluations. Ces modalités ne changent pas en cours de période.

Objectifs

  • Transpiler un fichier TypeScript et l’exécuter
  • Distinguer transpilation et compilation
  • Utiliser les types de base et les template literals
  • Créer un projet Astro et servir une page
  • Manipuler le DOM et réagir aux événements
  • Découper un script en modules

Du code au programme

Écrire du TypeScript

hello.ts
const message: string = "Hello World";
const age: number = 25;
const estActif: boolean = true;
const nombres: number[] = [1, 2, 3, 4, 5];
const fruits: Array<string> = ["pomme", "banane"];

La seule nouveauté face à JavaScript : les : string, : number, : boolean.

Ce sont des annotations de type. Elles disent au compilateur ce que chaque variable a le droit de contenir.

Transpiler, exécuter

tsc hello.ts     # produit hello.js
node hello.js    # exécute le JavaScript

Un .ts ne s’exécute jamais directement : il faut toujours deux temps.

hello.ts ──(tsc)──> hello.js ──(node)──> résultat
    │                                       ^
    └───────────────(tsx)───────────────────┘

Cinq commandes à ne pas confondre

Commande Ce qu’elle fait
node exécute du JavaScript hors navigateur
npm installe des paquets
npx lance un outil installé dans le projet
tsc traduit .ts → .js
tsx traduit et exécute d’un coup

npx répond à « où est l’outil ? ». tsc / tsx répondent à « qu’est-ce que j’en fais ? ».

npx, en une phrase

npm install range l’outil dans node_modules : il n’existe pas dans votre terminal pour autant.

npm install typescript --save-dev   # range tsc dans le projet
npx tsc hello.ts                    # va l'y chercher et le lance
Installé Vous tapez
globalement sur la machine tsc
dans le projet npx tsc

Machines de l’école : toujours npx.

tsc ou tsx ? Une lettre, deux effets

Commande Affiche quelque chose ? Laisse un .js ?
tsc hello.ts non oui
node hello.js oui non
tsx hello.ts oui non

Rien ne s’affiche après tsc ?

C’est normal : tsc produit un fichier, il n’exécute rien. Enchaînez avec node hello.js.

Gardez tsc en tête : c’est lui qui vérifie les types.

Transpilation ou compilation ?

Entrée Sortie
Compilation : gcc sur du C code source binaire, exécuté par le processeur
Transpilation : tsc sur du TypeScript code source code source, en JavaScript

tsc produit du JavaScript lisible. Ouvrez hello.js : vous comprendrez tout.

Les types s’évaporent

const message: string = "Hello World";   // hello.ts
var message = "Hello World";             // hello.js

Le JavaScript produit ne contient plus aucune trace du système de types. On parle d’effacement de types.

Conséquence décisive

Les types ne vous protègent pas à l’exécution. Ils vous protègent contre les incohérences de votre propre code, et cette protection s’arrête à la frontière du programme : JSON, réseau, formulaires.

Types composés

Type Description Exemple
tableau même type, taille libre number[] ou Array<number>
tuple taille et types fixés par position [string, number]
enum constantes nommées enum Couleur { Rouge, Vert }
const personne: [string, number] = ["Alice", 25];   // nom puis age, pas l'inverse

Types spéciaux

Type Sens Quand
void ne retourne rien fonctions à effet
null absence voulue valeur vidée explicitement
undefined jamais affectée rarement écrit à la main
unknown inconnu mais sûr JSON, réseau, formulaire
any désactive la vérification presque jamais
never ne retourne jamais lève toujours une erreur

any fait taire le compilateur : vous perdez tout le bénéfice de TypeScript. Face à l’inconnu, prenez unknown : il vous oblige à vérifier.

Types avancés

Notation Nom Exemple
| union let id: string | number;
type alias type Point = { x: number; y: number };
“a” | “b” types littéraux type Direction = “nord” | “sud”;

Direction n’accepte que ces deux chaînes, et l’éditeur vous les propose.

Les template literals

Écrites avec des backticks, elles remplacent la concaténation.

const nom = "Alice";
const age = 25;

const lourd = "Bonjour " + nom + ", tu as " + age + " ans.";
const clair = `Bonjour ${nom}, tu as ${age} ans.`;

Backtick : AltGr+7 sur AZERTY PC, Option+è sur Mac. C’est une touche morte : rien ne s’affiche avant la touche suivante.

Ce que permettent les backticks

console.log(`Total TTC : ${prixHT + tva}€`);          // un calcul
console.log(`Capitale : ${ville.toUpperCase()}`);      // un appel de méthode

const poeme = `Roses are red,
Violets are blue`;                                     // plusieurs lignes

Le $ seul ne suffit pas. `Bonjour $nom` affiche littéralement $nom. Les accolades sont obligatoires.

Vous venez du C ?

Concept C TypeScript
Variable int age = 25; const age: number = 25;
Chaîne char* nom = "Alice"; const nom: string = "Alice";
Tableau int tab[5]; const tab: number[] = [];
Fonction int add(int a, int b) function add(a: number, b: number): number
  • On déclare avec const ou let, jamais avec le type
  • Le type s’écrit après le nom, précédé de :
  • Un seul number : ni int, ni float
  • string est un vrai type, pas un tableau de caractères

À quoi sert vraiment le typage

const age: number = 25;   // annotation explicite
const nom = "Alice";      // inférence : TypeScript déduit string
  1. Les erreurs apparaissent à l’écriture, pas en production
  2. L’autocomplétion devient fiable
  3. Les types documentent, et ne mentent jamais

Débutant : annotez, cela force à réfléchir au type. Toujours : annotez les signatures de fonctions.

Une page web avec Astro

Du terminal au navigateur

Au chapitre 1.1, node exécutait le code dans un terminal. Ici, le même code s’exécute dans un navigateur et peut modifier la page affichée.

La porte d’entrée s’appelle document.

document n’existe que dans le navigateur

Un fichier qui manipule document et lancé avec node plante : ReferenceError: document is not defined.

Ce n’est pas une erreur de type, c’est une erreur d’environnement.

Le lien HTML / TypeScript

Tout en bas de la page :

<script src="app.js"></script>

On lie app.js, jamais app.ts : un navigateur ne lit pas le TypeScript. Sans l’étape tsc, la page s’affiche et rien ne réagit.

Ouvrez la console avec F12 : c’est là qu’écrit console.log, et là que s’affichent les erreurs.

Créer un projet Astro

npm create astro@latest mon-site
cd mon-site
npm run dev
Question de l’assistant Réponse
How would you like to start? Empty
Install dependencies? Yes
Initialize a git repository? au choix
Add AI agent files? No

Ouvrez l’adresse affichée, en général http://localhost:4321.

Structure du projet

mon-site/
├── src/
│   ├── pages/      # une page = un fichier .astro
│   └── scripts/    # vos fichiers .ts : à créer vous-même
└── package.json

Dans src/pages/index.astro, on charge directement le TypeScript :

<script src="../scripts/app.ts"></script>

Astro transpile à la volée et recharge la page à chaque enregistrement.

Le HTML utile

Rôle Balises
Structure <header> <nav> <main> <section> <footer>
Texte <h1>…<h6> <p> <span>
Conteneurs <div> (bloc), <span> (en ligne)
Listes <ul> <ol> <li>
Formulaires <form> <label> <input> <button>
Liens, images <a> <img>

<strong> et <b> donnent le même gras, mais <strong> dit pourquoi. Préférez toujours la balise sémantique.

id et class

Attribut Rôle
id identifiant unique : c’est par lui que TypeScript retrouve un élément
class étiquette partagée, ciblée par le CSS
href / src destination d’un lien, source d’une image ou d’un script
for relie un <label> au champ portant cet id

Le CSS en trois façons

<p style="color: red;">Texte rouge</p>          <!-- en ligne : pas réutilisable -->
<style> p { color: red; } </style>              <!-- dans le head : une page -->
<link rel="stylesheet" href="styles.css">       <!-- externe : la bonne méthode -->

Trois sélecteurs suffisent pour commencer :

p              { color: red; }        /* toutes les balises <p> */
.important     { font-weight: bold; } /* class="important"      */
#titre         { text-align: center; }/* id="titre"             */

Sélectionner un élément

const titre = document.getElementById("titre-principal") as HTMLHeadingElement;
if (titre) {
    console.log(titre.textContent);
}

as HTMLHeadingElement est une assertion de type : vous affirmez au compilateur ce qu’il ne peut pas deviner à partir d’une chaîne.

getElementById renvoie null si l’id n’existe pas : une faute de frappe suffit. L’assertion as ne vérifie rien à l’exécution. D’où le if systématique.

Modifier contenu et style

element.textContent = "Nouveau texte";         // sûr : traité comme du texte
element.innerHTML = "<b>Gras</b>";             // interprète le HTML

element.style.color = "purple";
element.style.backgroundColor = "#f0f0f0";     // camelCase, pas background-color

innerHTML et données extérieures

Une chaîne venue d’un formulaire, d’une URL ou d’une API peut contenir du code qui s’exécutera dans la page : c’est une faille XSS.

textContent par défaut. innerHTML seulement pour du HTML que vous écrivez.

Créer et insérer

const titre: HTMLHeadingElement = document.createElement("h1");
titre.textContent = "Hello TypeScript";
titre.style.color = "purple";
document.body.prepend(titre);   // prepend : au début ; appendChild : à la fin

Un élément créé n’apparaît nulle part tant qu’il n’est pas inséré.

C’est l’oubli le plus fréquent : le code tourne sans erreur, et rien ne s’affiche.

Réagir aux événements

let compteur: number = 0;
const affichage = document.getElementById("compteur") as HTMLParagraphElement;
const bouton = document.getElementById("btn") as HTMLButtonElement;

if (bouton && affichage) {
    bouton.addEventListener("click", function () {
        compteur = compteur + 1;
        affichage.textContent = `Compteur : ${compteur}`;
    });
}

Vous n’appelez jamais cette fonction : c’est le navigateur qui l’appellera. On parle de fonction de rappel.

L’événement porte de l’information

champ.addEventListener("input", function (event: Event) {
    const cible = event.target as HTMLInputElement;   // l'élément déclencheur
    sortie.textContent = `Vous avez tapé : ${cible.value}`;
});
Événement Déclencheur
click clic sur l’élément
input saisie, à chaque caractère
change valeur modifiée, champ quitté
submit envoi d’un formulaire
keydown / keyup touche pressée / relâchée

Modules

Pourquoi découper

Un module est simplement un fichier .ts qui exporte certains éléments et en importe d’autres.

  1. Portée isolée : ce qui n’est pas exporté est invisible dehors
  2. Dépendances explicites : la ligne import dit de quoi le fichier a besoin

Une fonction non exportée correspond à une fonction static en C : l’isolement est la règle, l’exposition l’exception.

Exporter, importer

src/scripts/moduleMath.ts
export const pi = 3.14159;

export function additionner(a: number, b: number): number {
    return a + b;
}
src/scripts/app.ts
import { pi, additionner } from "./moduleMath.ts";

console.log(additionner(pi, 1));   // 4.14159

Le ./ n’est pas décoratif : sans lui, on cherche un paquet dans node_modules. La page ne charge que app.ts ; Astro suit la chaîne des import tout seul.

Les quatre formes

Forme Dans le module Chez l’appelant
Nommé (recommandé) export const pi = 3.14; import { pi } from "./m.ts";
Par défaut export default function f() {} import f from "./m.ts";
Renommage export const pi = 3.14; import { pi as PI } from "./m.ts";
Global (rien) import * as M from "./m.ts"; puis M.pi

Conflit de noms : as

import { calculerRestants as restantsVacances } from "./moduleVacances.ts";
import { calculerRestants as restantsTravail } from "./moduleTravail.ts";

Un import déclare un nom dans votre fichier. Deux fois le même nom donne Duplicate identifier : le compilateur ne peut pas choisir à votre place.

C’est au fichier qui importe, le seul à voir les deux modules, de trancher.

À vous : par où commencer

Parcours essentiel, dans l’ordre

  • 1.1 Observer la transpilation
  • 1.2 Fiche d’identité d’une école (transfert)
  • 1.3 Provoquer des erreurs volontairement
  • 1.4 Explorer la page d’exemple
  • 1.5 Une fiche interactive dans un projet Astro (transfert)
  • 1.7 Étendre le module de calcul
  • 1.8 Une troisième source, sans toucher au reste (transfert)

Ensuite

  • 1.6 Liste construite en TypeScript (entraînement)
  • 1.9 Les fichiers de départ, écrits vous-même (approfondissement)

Tapez les exemples, ne les lisez pas. Les corrigés sont dépliables sous chaque énoncé : ouvrez-les après avoir essayé.