Back

TSX et l'essor des composants frontend typés

TSX et l'essor des composants frontend typés

Vous développez un composant React. Vous passez une chaîne de caractères là où un nombre est attendu. TypeScript le détecte avant le navigateur. Cette interaction simple—les types prévenant les erreurs d’exécution—explique pourquoi TSX est devenu le format par défaut pour le développement frontend moderne.

Cet article couvre ce que signifient concrètement les composants frontend typés : le typage des props, la gestion des événements, les patterns pour les children, et comment l’inférence TypeScript élimine le code superflu. Nous aborderons également comment la séparation entre composants serveur et client de React 19 affecte votre stratégie de typage.

Points clés à retenir

  • Les composants typés TSX détectent les incompatibilités de types à la compilation, réduisant les erreurs d’exécution avant que le code n’atteigne le navigateur.
  • L’inférence TypeScript gère automatiquement la plupart des typages d’événements—les annotations explicites ne sont nécessaires que lors de l’extraction des handlers vers des fonctions séparées.
  • Les unions discriminées permettent des composants type-safe avec des comportements mutuellement exclusifs, où TypeScript affine les types en fonction d’une propriété discriminante.
  • La frontière serveur/client de React 19 introduit des contraintes de sérialisation : les props passées des composants serveur aux composants client doivent être sérialisables (pas de fonctions, classes, symboles ou autres valeurs non sérialisables).

Ce que signifient réellement les composants typés TSX

Un composant typé n’est pas simplement un composant avec des props typées. C’est un composant où TypeScript comprend l’intégralité du contrat : ce qui entre, ce qui sort, et ce qui se passe entre les deux.

Avec la transformation JSX moderne (react-jsx), vous n’importez plus React dans chaque fichier. Vous écrivez du TSX, et la chaîne d’outils gère le reste. Vite configure cela correctement dès le départ :

interface ButtonProps {
  label: string
  disabled?: boolean
}

function Button({ label, disabled = false }: ButtonProps) {
  return <button disabled={disabled}>{label}</button>
}

Remarquez qu’il n’y a pas de React.FC ici. Ce pattern est optionnel—et souvent inutile. Typer les props directement sur le paramètre de la fonction est plus propre et évite la prop implicite children que React.FC ajoute, que vous la vouliez ou non.

Typage des props et sécurité des types React TSX

Le typage des props est le point de départ pour la plupart des développeurs, mais la sécurité des types React TSX va plus loin. Considérez les unions discriminées pour les composants avec des comportements mutuellement exclusifs :

type LinkButtonProps = 
  | { variant: 'button'; onClick: () => void }
  | { variant: 'link'; href: string }

function LinkButton(props: LinkButtonProps) {
  if (props.variant === 'link') {
    return <a href={props.href}>Navigate</a>
  }
  return <button onClick={props.onClick}>Click</button>
}

TypeScript affine le type en fonction de variant. Vous ne pouvez pas accéder accidentellement à href quand variant vaut 'button'. Ce pattern s’adapte bien aux API de composants complexes.

Typage des événements sans code superflu

Les gestionnaires d’événements posent souvent problème aux développeurs débutant avec TypeScript. La bonne nouvelle : l’inférence gère automatiquement la plupart des cas.

import { useState } from 'react'

function SearchInput() {
  const [query, setQuery] = useState('')

  // TypeScript infère le type d'événement depuis le contexte
  return (
    <input 
      value={query} 
      onChange={(e) => setQuery(e.currentTarget.value)} 
    />
  )
}

Lorsque vous extrayez les handlers vers des fonctions séparées, vous aurez besoin de types explicites :

import type { ChangeEvent } from 'react'

function handleChange(event: ChangeEvent<HTMLInputElement>) {
  // event.currentTarget.value est correctement typé comme string
}

Typage des children : ReactNode vs ReactElement

Pour les children, React.ReactNode couvre le cas courant—tout ce qui est affichable :

import type { ReactNode } from 'react'

interface CardProps {
  children: ReactNode
}

Utilisez React.ReactElement lorsque vous avez besoin strictement d’éléments JSX, excluant les chaînes de caractères et les nombres. Pour les composants qui acceptent des children, PropsWithChildren est une alternative plus propre que d’ajouter manuellement la prop children :

import type { PropsWithChildren } from 'react'

interface CardProps {
  title: string
}

function Card({ title, children }: PropsWithChildren<CardProps>) {
  return (
    <div>
      <h2>{title}</h2>
      {children}
    </div>
  )
}

Typage des composants serveur et client dans React 19

Les projets React 19 TypeScript, en particulier ceux utilisant Next.js App Router, divisent les composants en frontières serveur et client. Cela affecte le typage.

Les composants serveur peuvent être asynchrones et récupérer les données directement :

// Composant serveur - aucune directive nécessaire (par défaut dans App Router)
async function UserProfile({ userId }: { userId: string }) {
  const user = await fetchUser(userId)
  return <div>{user.name}</div>
}

Les composants client nécessitent la directive 'use client' et peuvent utiliser les hooks :

'use client'

import { useState } from 'react'

interface CounterProps {
  initialCount: number
}

function Counter({ initialCount }: CounterProps) {
  const [count, setCount] = useState(initialCount)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

La frontière de typage est importante : les props passées des composants serveur aux composants client doivent être sérialisables. Les fonctions, classes, symboles et valeurs non sérialisables (comme les instances de classe ou les Dates) ne franchissent pas cette frontière.

Actions de formulaire et workflows typés modernes

React 19 introduit les server actions (couramment utilisées avec les formulaires) comme pattern typé pour gérer les soumissions, généralement définies dans des fichiers serveur et invoquées depuis des composants client :

async function submitForm(formData: FormData) {
  'use server'
  const email = formData.get('email')
  if (typeof email !== 'string') {
    throw new Error('Email is required')
  }
  // Traiter la soumission
}

Cela s’intègre aux composants frontend typés en maintenant la gestion des formulaires consciente des types du client au serveur.

Conclusion

Les composants typés TSX réduisent les bugs en détectant les incompatibilités à la compilation. L’inférence TypeScript minimise le code superflu—vous avez rarement besoin d’annotations de type explicites pour les hooks ou les handlers inline. La séparation serveur/client dans React 19 ajoute une nouvelle considération : les frontières de sérialisation.

Évitez React.FC sauf si vous avez une raison spécifique. Typez les props directement. Laissez l’inférence fonctionner. Votre éditeur vous remerciera avec une autocomplétion précise et un retour d’erreur immédiat.

FAQ

Typez les props directement sur les paramètres de fonction. React.FC ajoute une prop children implicite, que vous la vouliez ou non, et typer les props directement est plus propre. L'équipe React ne recommande pas React.FC comme pattern par défaut. Utilisez-le uniquement lorsque vous avez spécifiquement besoin de son comportement, par exemple lors du travail avec des bases de code legacy qui l'attendent.

Pour les handlers inline, TypeScript infère automatiquement le type d'événement depuis le contexte. Lorsque vous extrayez les handlers vers des fonctions séparées, utilisez des types explicites comme React.ChangeEvent pour les changements d'input ou React.MouseEvent pour les clics. Le paramètre générique spécifie le type d'élément, tel que HTMLInputElement ou HTMLButtonElement.

ReactNode accepte tout ce qui est affichable, y compris les chaînes de caractères, les nombres, null, undefined et les éléments JSX. ReactElement accepte uniquement les éléments JSX, excluant les primitives. Utilisez ReactNode pour la plupart des cas. Utilisez ReactElement lorsque votre composant nécessite spécifiquement des children JSX et doit rejeter le texte brut ou les nombres.

Non. Les props passées des composants serveur aux composants client doivent être sérialisables. Les fonctions, classes, symboles et autres valeurs non sérialisables ne peuvent pas franchir la frontière serveur-client. Définissez les gestionnaires d'événements et callbacks au sein des composants client, ou utilisez les server actions pour les soumissions de formulaires nécessitant un traitement côté serveur.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay