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.
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 ?? 3Affectation 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 onlyModè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.