Vinicius Aguiar
TypeScript

Débuter en TypeScript : guide pratique pour les devs JavaScript

16 avril 2026 · 12 min de lecture

Si vous écrivez du JavaScript et que vous n'utilisez pas encore TypeScript, vous vous privez de l'outil le plus efficace pour éviter les bugs en production. TypeScript n'est pas un langage différent — c'est du JavaScript avec un typage statique. Le compilateur vous signale les erreurs avant que le code ne s'exécute, pas après. Dans ce guide, je présente tout ce dont vous avez besoin pour commencer à utiliser TypeScript dans des projets réels.

Pourquoi TypeScript

JavaScript est dynamique — n'importe quelle variable peut être n'importe quoi à n'importe quel moment. C'est flexible, mais dangereux. Vous ne découvrez les erreurs que lorsque le code s'exécute et que l'utilisateur voit l'erreur.

// JavaScript : ceci s'exécute sans erreur
function calculateTotal(price, quantity) {
  return price * quantity
}

calculateTotal('10', 2) // Renvoie '102' (string), pas 20
calculateTotal(10)      // Renvoie NaN, sans avertissement

TypeScript attrape ces erreurs avant l'exécution :

// TypeScript : le compilateur vous avertit
function calculateTotal(price: number, quantity: number): number {
  return price * quantity
}

calculateTotal('10', 2) // ✗ Erreur : Argument of type 'string' is not assignable to 'number'
calculateTotal(10)      // ✗ Erreur : Expected 2 arguments, but got 1

Types de base

Les types primitifs de TypeScript correspondent directement aux types de JavaScript :

// Primitifs
let name: string = 'Vinicius'
let age: number = 24
let isActive: boolean = true

// Arrays
let tags: string[] = ['react', 'next', 'typescript']
let scores: number[] = [95, 87, 92]

// Objets inline
let user: { name: string; email: string } = {
  name: 'Vinicius',
  email: 'vini@email.com'
}

En pratique, vous n'avez presque jamais besoin de typer explicitement les variables — TypeScript infère le type automatiquement :

// Inférence automatique — TS sait que c'est une string
let name = 'Vinicius' // type : string
let age = 24           // type : number
let tags = ['react']   // type : string[]

// Ne typer explicitement que si nécessaire
let data: unknown = await fetchFromAPI()

Interfaces et Types

Pour des objets plus complexes, utilisez interface ou type. La différence pratique est minime — utilisez interface pour les objets et type pour les unions et les types composés :

// Interface — pour les objets
interface User {
  id: string
  name: string
  email: string
  role: 'admin' | 'user'
  avatar?: string // champ optionnel
}

// Type — pour les unions et les compositions
type Status = 'pending' | 'confirmed' | 'failed' | 'refunded'

type ApiResponse<T> = {
  data: T
  success: boolean
  error: string | null
}

Les interfaces peuvent être étendues, ce qui est utile pour l'héritage de types :

interface BaseEntity {
  id: string
  createdAt: Date
  updatedAt: Date
}

interface User extends BaseEntity {
  name: string
  email: string
}

interface Order extends BaseEntity {
  userId: string
  total: number
  status: Status
}

// User a maintenant id, createdAt, updatedAt, name, email

Fonctions typées

C'est dans le typage des fonctions que TypeScript brille le plus. Le compilateur valide les arguments et le retour :

// Paramètres et retour typés
function createUser(name: string, email: string): User {
  return {
    id: crypto.randomUUID(),
    name,
    email,
    role: 'user',
    createdAt: new Date(),
    updatedAt: new Date()
  }
}

// Arrow function
const formatPrice = (cents: number): string => {
  return `R$ ${(cents / 100).toFixed(2)}`
}

// Fonction asynchrone
async function getUser(id: string): Promise<User | null> {
  const res = await fetch(`/api/users/${id}`)
  if (!res.ok) return null
  return res.json()
}

Generics : des types réutilisables

Les generics permettent de créer des fonctions et des types qui fonctionnent avec n'importe quel type, tout en conservant la sécurité. C'est le concept le plus puissant de TypeScript :

// Sans generics — il faudrait une fonction par type
function getFirstString(arr: string[]): string | undefined {
  return arr[0]
}
function getFirstNumber(arr: number[]): number | undefined {
  return arr[0]
}

// Avec generics — une fonction pour tous les types
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0]
}

getFirst(['a', 'b', 'c']) // type : string | undefined
getFirst([1, 2, 3])       // type : number | undefined
getFirst<User>(users)     // type : User | undefined

Les generics sont essentiels pour les fonctions d'API, les hooks React et tout code réutilisable :

// Réponse d'API générique
async function fetchAPI<T>(endpoint: string): Promise<ApiResponse<T>> {
  const res = await fetch(`/api${endpoint}`)
  const data = await res.json()
  return data as ApiResponse<T>
}

// Utilisation — TS connaît le type de retour
const { data: user } = await fetchAPI<User>('/users/123')
// user est de type User

const { data: orders } = await fetchAPI<Order[]>('/orders')
// orders est de type Order[]

Utility Types : transformer les types

TypeScript est livré avec des utility types qui transforment les types existants. Voici ceux que j'utilise au quotidien :

interface User {
  id: string
  name: string
  email: string
  role: 'admin' | 'user'
}

// Partial — tous les champs deviennent optionnels
type UpdateUser = Partial<User>
// { id?: string; name?: string; email?: string; role?: ... }

// Pick — sélectionne des champs spécifiques
type UserPreview = Pick<User, 'id' | 'name'>
// { id: string; name: string }

// Omit — retire des champs spécifiques
type CreateUser = Omit<User, 'id'>
// { name: string; email: string; role: 'admin' | 'user' }

// Record — crée un objet typé
type UserMap = Record<string, User>
// { [key: string]: User }

L'usage le plus courant se trouve dans les formulaires et les API — là où vous avez besoin de variations du même type :

// API : créer un utilisateur (sans id, la base de données le génère)
async function createUser(data: Omit<User, 'id'>): Promise<User> {
  const res = await fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify(data)
  })
  return res.json()
}

// API : mettre à jour partiellement (tous les champs optionnels)
async function updateUser(id: string, data: Partial<User>): Promise<User> {
  const res = await fetch(`/api/users/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(data)
  })
  return res.json()
}

Enums vs Union Types

Les enums existent en TypeScript mais, en pratique, les union types sont plus simples et ne génèrent pas de code JavaScript supplémentaire :

// ❌ Enum — génère du code JS supplémentaire, plus complexe
enum PaymentStatus {
  PENDING = 'pending',
  CONFIRMED = 'confirmed',
  FAILED = 'failed',
}

// ✅ Union type — plus simple, zéro overhead
type PaymentStatus = 'pending' | 'confirmed' | 'failed'

// Les deux fonctionnent pareil pour la validation :
function processPayment(status: PaymentStatus) {
  switch (status) {
    case 'pending':
      return 'En attente...'
    case 'confirmed':
      return 'Confirmé !'
    case 'failed':
      return 'Échec.'
  }
}

Type guards : affiner les types

Quand une valeur peut être de plusieurs types, les type guards aident TypeScript à comprendre de quel type il s'agit à chaque moment :

type Result = { success: true; data: User } | { success: false; error: string }

function handleResult(result: Result) {
  if (result.success) {
    // TS sait que result.data existe ici
    console.log(result.data.name)
  } else {
    // TS sait que result.error existe ici
    console.error(result.error)
  }
}

// Type guard personnalisé
function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'name' in value &&
    'email' in value
  )
}

const data: unknown = await fetchSomething()
if (isUser(data)) {
  // TS sait que data est un User ici
  console.log(data.email)
}

Configuration : tsconfig.json

Le tsconfig.json définit le comportement de TypeScript dans le projet. Pour des projets modernes avec Next.js ou Node.js, voici la base que je recommande :

{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "incremental": true,
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Le plus important est "strict": true — il active toutes les vérifications de type. Les projets sans strict mode perdent la plus grande partie de la valeur de TypeScript.

Les patterns que j'utilise en production

Après avoir utilisé TypeScript dans tous les projets, voici les patterns que j'applique le plus au quotidien :

  • **Ne jamais utiliser any** — utilisez unknown quand vous ne connaissez pas le type et faites un type guard pour valider
  • L'inférence d'abord — laissez TS inférer le type chaque fois que c'est possible, ne typez explicitement que si nécessaire
  • Des interfaces pour les props de composants — toute prop de composant React doit avoir une interface
  • Des utility types pour les APIOmit<User, 'id'> pour la création, Partial<User> pour l'update
  • Des union types au lieu des enums — plus simples, sans overhead de runtime
  • **as const** — pour les arrays et les objets littéraux qui ne doivent pas changer
// as const — transforme des valeurs en types littéraux
const ROLES = ['admin', 'user', 'moderator'] as const
type Role = typeof ROLES[number] // 'admin' | 'user' | 'moderator'

// Props de composant React
interface ButtonProps {
  label: string
  variant?: 'primary' | 'secondary' | 'ghost'
  onClick: () => void
  disabled?: boolean
}

function Button({ label, variant = 'primary', onClick, disabled }: ButtonProps) {
  return (
    <button onClick={onClick} disabled={disabled} className={variant}>
      {label}
    </button>
  )
}

Conclusion

TypeScript ne consiste pas à écrire plus de code — il s'agit d'écrire du code que le compilateur peut valider. Les types documentent l'intention, les interfaces définissent des contrats entre modules, et les generics permettent la réutilisation sans perdre en sécurité. Si vous venez de JavaScript, la courbe d'apprentissage est douce : commencez par typer les fonctions et les props, puis avancez vers les generics et les utility types. Le gain en productivité et en confiance dans le code apparaît vite.