TypeScript et JavaScript pour les experts : fonctionnalités modernes et architecture

· 3 min de lecture

Les API JavaScript modernes et les motifs de types TypeScript que tout développeur front-end expert devrait utiliser.

typescriptjavascriptfrontendarchitecture

JavaScript gagne sans cesse de petites fonctionnalités précises, et TypeScript transforme sans cesse les erreurs d'exécution en erreurs de compilation. Ce guide couvre ce qui compte pour du code front-end de production aujourd'hui, avec des exemples.

flowchart TD
    JS[JavaScript] --> A[Modern built-ins]
    A --> A1[toSorted / at / groupBy]
    A --> A2[Promise.withResolvers]
    TS[TypeScript] --> B[The type system]
    B --> B1[Discriminated unions]
    B --> B2[Utility types]
    TS --> C[Architecture]
    C --> C1[Result types]
    C --> C2[Exhaustive checks]

JavaScript : les essentiels modernes

Chaînage optionnel et coalescence des nuls

Lire des données imbriquées sans exploser, et ne retomber que sur null/undefined.

const city = user?.address?.city
const label = name ?? 'Anonymous'
const retries = config?.retries ?? 3

Affectation logique

Ne modifier une variable que lorsqu'elle est fausse, nulle ou vraie.

user.nickname ||= user.firstName
settings.theme ??= 'dark'

Méthodes de tableau non mutantes

toSorted, toReversed, toSpliced et with renvoient de nouveaux tableaux au lieu de muter, ce qui se marie parfaitement avec l'état immuable.

const sorted = items.toSorted((a, b) => a.score - b.score)
const reversed = items.toReversed()
const first = items.at(0)
const last = items.at(-1)

Groupement et clonage

Object.groupBy répartit une collection en une ligne, et structuredClone fait une copie profonde sans bidouillage JSON.

const byStatus = Object.groupBy(orders, (o) => o.status)

const copy = structuredClone(original)

Aides pour les promesses

Promise.withResolvers vous donne les fonctions resolve/reject sans envelopper une promesse dans un constructeur.

const { promise, resolve, reject } = Promise.withResolvers<string>()

async function nextMessage(): Promise<string> {
  return promise
}

Array.fromAsync transforme un itérable asynchrone en tableau.

const lines = await Array.fromAsync(stream)

Promesses et Observables

Promesses

Une promesse représente une valeur qui sera disponible plus tard. async/await aplatit les callbacks.

function fetchUser(id: number): Promise<User> {
  return fetch(`/api/users/${id}`).then((res) => res.json())
}

async function loadUser(id: number): Promise<User | null> {
  try {
    return await fetchUser(id)
  } catch (error) {
    console.error('failed to load user', error)
    return null
  }
}

Exécutez des travaux indépendants en parallèle et récupérez les résultats :

const [a, b, c] = await Promise.all([
  fetchUser(1),
  fetchUser(2),
  fetchUser(3),
])

Observables

Les Observables (RxJS) modélisent un flux de valeurs dans le temps. Construisez des pipelines d'opérateurs et désabonnez-vous toujours.

import { from, fromEvent } from 'rxjs'
import { debounceTime, filter, map, switchMap } from 'rxjs/operators'

const search$ = fromEvent<InputEvent>(input, 'input').pipe(
  debounceTime(300),
  map((event) => (event.target as HTMLInputElement).value),
  filter((term) => term.length >= 3),
  switchMap((term) =>
    from(fetch(`/api/search?q=${term}`).then((res) => res.json())),
  ),
)

const subscription = search$.subscribe({
  next: (results) => console.log(results),
  error: (err) => console.error(err),
})

subscription.unsubscribe()

Une promesse produit une valeur ; un observable en produit plusieurs dans le temps. Utilisez les promesses pour des résultats uniques, les observables pour des événements et des flux.

TypeScript : le système de types qui compte

unknown bat any

any désactive le vérificateur de types ; unknown vous oblige à rétrécir d'abord.

function parse(value: unknown): string {
  if (typeof value === 'string') return value
  throw new Error('not a string')
}

satisfies et as const

satisfies vérifie un objet contre un type sans l'élargir, et as const fige les valeurs sur leurs types littéraux.

const palette = {
  primary: '#c770f0',
  danger: '#ef4444',
} satisfies Record<string, string>

const routes = ['/', '/about', '/blog'] as const
type Route = (typeof routes)[number]

Unions discriminées

Modélisez chaque état avec un champ kind partagé, et le compilateur rétrécit pour vous — le socle des reducers et machines à états typés.

type State =
  | { kind: 'idle' }
  | { kind: 'loading' }
  | { kind: 'success'; data: User[] }
  | { kind: 'error'; message: string }

function render(state: State): string {
  switch (state.kind) {
    case 'idle': return 'Waiting…'
    case 'loading': return 'Loading…'
    case 'success': return state.data.map((u) => u.name).join(', ')
    case 'error': return state.message
  }
}

Prédicats de type

Rétrécissez unknown vers une forme concrète en toute sécurité.

function isUser(value: unknown): value is User {
  return typeof value === 'object' && value !== null && 'id' in value
}

Types template littéraux

Construisez des types de chaînes à partir d'unions — idéal pour les routes et chemins d'API typés.

type Route = `/api/${'users' | 'orders'}/${string}`

Paramètres de type const

Gardez les littéraux de tuples précis sans saupoudrer as const partout.

function preserve<const T>(items: T[]): T[] {
  return items
}

const pair = preserve([1, 'two']) // [number, string]

Types utilitaires

Composez de nouveaux types à partir d'existants au lieu de les écrire à la main.

type UserInput = Omit<User, 'id' | 'createdAt'>
type PartialUser = Partial<User>
type ReadonlyUser = Readonly<User>

Fonctionnalités TypeScript récentes

Décorateurs standard

Les décorateurs font désormais partie du langage, sans drapeau expérimental.

function logged(target: unknown, context: ClassMethodDecoratorContext) {
  // wrap the method
}

class Service {
  @logged
  fetchData() {
    /* ... */
  }
}

Gestion explicite des ressources

using exécute le nettoyage de manière déterministe via Symbol.dispose.

function readFile(path: string) {
  using handle = openFile(path)
  return handle.read()
}

NoInfer

Empêchez le compilateur d'inférer un argument de type à une position précise.

declare function createPair<T>(first: T, second: NoInfer<T>): T

const result = createPair('a', 'b') // T is inferred from the first arg only

Modèles d'architecture

Types Result au lieu d'exceptions

Représentez le succès et l'échec dans le type, pour que les appelants ne puissent pas oublier de gérer les erreurs.

type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E }

function divide(a: number, b: number): Result<number> {
  if (b === 0) return { ok: false, error: new Error('division by zero') }
  return { ok: true, value: a / b }
}

Vérifications exhaustives

Un utilitaire never garantit que chaque cas est traité quand l'union grandit.

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`)
}

// switch (state.kind) { ... default: assertNever(state) }

Types marqués (branded types)

Évitez de confondre des identifiants qui partagent le même type sous-jacent.

type UserId = string & { readonly __brand: 'UserId' }
type OrderId = string & { readonly __brand: 'OrderId' }

function toUserId(id: string): UserId {
  return id as UserId
}

Immutabilité par défaut

Préférez readonly, as const et les méthodes non mutantes pour que les changements d'état soient explicites.

interface Config {
  readonly apiUrl: string
  readonly retries: number
}

ESM et tree-shaking

Utilisez les modules ES et les imports nommés pour que les bundlers puissent supprimer le code inutilisé, et préférez les imports de type pour que les types ne s'infiltrent jamais dans les bundles d'exécution.

import type { User } from './models'
import { fetchUsers } from './api'

Pour conclure

L'expert front-end moderne s'appuie sur les nouvelles API JavaScript pour le traitement des données, et sur les unions discriminées, les types utilitaires et les types Result de TypeScript pour repousser les erreurs à la compilation. Adoptez ces motifs et votre code devient plus petit, plus sûr et bien plus facile à raisonner.