Back

React & TypeScript : Modèles Courants pour un Code Plus Propre

React & TypeScript : Modèles Courants pour un Code Plus Propre

Lors du développement d’applications React avec TypeScript, vous rencontrerez les mêmes défis de manière répétée : comment typer les props correctement, gérer les événements en toute sécurité, et structurer les composants pour une réutilisabilité maximale. Ces modèles ne servent pas seulement à satisfaire le compilateur—ils permettent d’écrire du code plus facile à comprendre, refactoriser et maintenir.

Cet article couvre les modèles React TypeScript les plus pratiques que vous utiliserez au quotidien. Vous apprendrez à typer les composants efficacement, gérer les props optionnelles, travailler avec les événements et les refs, exploiter les types utilitaires, et utiliser les unions discriminées dans les reducers. Chaque modèle inclut des exemples ciblés montrant exactement pourquoi il améliore votre code.

Points Clés à Retenir

  • Typez explicitement les props et l’état en utilisant des interfaces ou des types pour un meilleur IntelliSense et une détection d’erreurs améliorée
  • Gérez les props optionnelles et par défaut avec les propriétés optionnelles de TypeScript et les paramètres par défaut
  • Utilisez les types utilitaires comme Pick et ComponentProps pour réduire la répétition et maintenir la cohérence
  • Typez correctement les événements et refs pour des interactions DOM type-safe
  • Exploitez les unions discriminées dans les reducers pour une gestion exhaustive des actions
  • Composez les composants avec forwardRef pour maintenir la sécurité de type lors du passage de refs

Typage des Props et de l’État

La base des modèles React TypeScript commence par le typage approprié des props de vos composants. Cela permet de détecter les erreurs à la compilation et fournit une meilleure autocomplétion dans votre éditeur.

Modèle de Props de Base

interface ButtonProps {
  label: string
  onClick: () => void
  disabled?: boolean
}

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

Ce modèle garantit que vos composants reçoivent les bons types de données. L’interface documente clairement les props attendues par le composant, le rendant auto-documenté.

Typage de l’État avec useState

TypeScript infère généralement les types d’état correctement, mais un état complexe bénéficie d’un typage explicite :

import { useState } from 'react'

interface User {
  id: string
  name: string
  email: string
}

function UserProfile() {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(false) // Type inféré comme boolean

  // TypeScript sait que user peut être null
  return user ? <div>{user.name}</div> : <div>Chargement...</div>
}

Gestion des Props Optionnelles et par Défaut

Les composants React ont souvent des props de configuration optionnelles. Les propriétés optionnelles de TypeScript combinées aux paramètres par défaut créent un modèle propre :

interface CardProps {
  title: string
  description?: string
  variant?: 'primary' | 'secondary'
  className?: string
}

function Card({ 
  title, 
  description, 
  variant = 'primary',
  className = ''
}: CardProps) {
  return (
    <div className={`card card-${variant} ${className}`}>
      <h2>{title}</h2>
      {description && <p>{description}</p>}
    </div>
  )
}

Ce modèle rend les composants flexibles tout en maintenant la sécurité de type. Les props requises sont claires, et les props optionnelles ont des valeurs par défaut sensées.

Typage des Événements et Refs

Les gestionnaires d’événements et les refs sont des sources courantes d’erreurs TypeScript. Ces modèles éliminent les approximations :

Modèle de Gestionnaire d’Événements

import { useState, ChangeEvent, FormEvent } from 'react'

interface FormProps {
  onSubmit: (data: { email: string; password: string }) => void
}

function LoginForm({ onSubmit }: FormProps) {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
    setEmail(e.target.value)
  }

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    onSubmit({ email, password })
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={handleEmailChange} />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      <button type="submit">Connexion</button>
    </form>
  )
}

Modèle de Ref

import { useRef, useEffect } from 'react'

function AutoFocusInput() {
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    // TypeScript sait que inputRef.current peut être null
    inputRef.current?.focus()
  }, [])

  return <input ref={inputRef} type="text" />
}

Utilisation des Types Utilitaires

Les types utilitaires de TypeScript réduisent la duplication de code et maintiennent les types synchronisés dans votre application.

Modèle ComponentProps

Extrayez les props de composants existants sans répéter les définitions de types :

import { ComponentProps } from 'react'

// Obtenez toutes les props qu'un élément button accepte
type ButtonProps = ComponentProps<'button'> & {
  variant?: 'primary' | 'secondary'
}

function StyledButton({ variant = 'primary', ...props }: ButtonProps) {
  return <button className={`btn-${variant}`} {...props} />
}

// Extrayez les props de vos propres composants
type MyButtonProps = ComponentProps<typeof StyledButton>

Modèle Pick

Sélectionnez des props spécifiques lors de l’extension de composants :

interface UserData {
  id: string
  name: string
  email: string
  role: string
  lastLogin: Date
}

// Seules certaines propriétés sont nécessaires pour la carte
type UserCardProps = Pick<UserData, 'name' | 'email'> & {
  onClick?: () => void
}

function UserCard({ name, email, onClick }: UserCardProps) {
  return (
    <div onClick={onClick}>
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  )
}

Composition de Composants avec forwardRef

Lors de la construction de bibliothèques de composants ou de l’encapsulation d’éléments natifs, forwardRef maintient la sécurité de type tout en exposant les refs :

import { forwardRef, useRef, InputHTMLAttributes } from 'react'

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  label: string
  error?: string
}

const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ label, error, ...props }, ref) => {
    return (
      <div>
        <label>{label}</label>
        <input ref={ref} {...props} />
        {error && <span className="error">{error}</span>}
      </div>
    )
  }
)

Input.displayName = 'Input'

// L'utilisation maintient une sécurité de type complète
function Form() {
  const inputRef = useRef<HTMLInputElement>(null)
  
  return <Input ref={inputRef} label="Email" type="email" />
}

Unions Discriminées dans les Reducers

Les unions discriminées rendent les actions de reducer type-safe et permettent une vérification exhaustive :

interface TodoState {
  items: Array<{ id: string; text: string; done: boolean }>
  filter: 'all' | 'active' | 'completed'
}

type TodoAction =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: string }
  | { type: 'SET_FILTER'; payload: TodoState['filter'] }
  | { type: 'CLEAR_COMPLETED' }

function todoReducer(state: TodoState, action: TodoAction): TodoState {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        items: [...state.items, {
          id: Date.now().toString(),
          text: action.payload,
          done: false
        }]
      }
    
    case 'TOGGLE_TODO':
      return {
        ...state,
        items: state.items.map(item =>
          item.id === action.payload
            ? { ...item, done: !item.done }
            : item
        )
      }
    
    case 'SET_FILTER':
      return { ...state, filter: action.payload }
    
    case 'CLEAR_COMPLETED':
      return {
        ...state,
        items: state.items.filter(item => !item.done)
      }
    
    default:
      // TypeScript s'assure que tous les cas sont gérés
      const exhaustive: never = action
      return state
  }
}

Ce modèle garantit que vous gérez tous les types d’actions. Si vous ajoutez un nouveau type d’action, TypeScript générera une erreur jusqu’à ce que vous le gériez dans le reducer.

Mises à Jour React 19

Avec React 19, les nouvelles capacités comme les Actions (useActionState, useFormStatus), l’API use() pour consommer des promesses ou du contexte, et le support de première classe pour les Server Components rendent la sécurité de type encore plus importante. Lors du typage des Actions, définissez explicitement la forme des FormData et la valeur de retour de l’action afin que le code serveur et client restent synchronisés. Le passage prochain de forwardRef vers le passage de ref comme prop standard simplifie également le typage—traitez ref comme n’importe quelle autre prop et laissez TypeScript assurer la correction à travers les frontières des composants.

Conclusion

Ces modèles React TypeScript courants forment la base d’applications React type-safe. En typant explicitement les props et l’état, en gérant correctement les événements et refs, et en exploitant les types utilitaires de TypeScript, vous créez du code à la fois plus sûr et plus maintenable. Les modèles fonctionnent ensemble—un typage approprié des props permet une meilleure composition de composants, tandis que les unions discriminées garantissent une gestion exhaustive dans la logique d’état complexe.

Commencez par le typage de base des props et incorporez progressivement les types utilitaires et modèles avancés à mesure que votre application grandit. L’investissement dans un typage approprié est rentabilisé par un meilleur support IDE, moins d’erreurs d’exécution, et des contrats de composants plus clairs.

FAQ

Utilisez interface quand vous pourriez avoir besoin d'étendre ou fusionner des déclarations plus tard. Utilisez type pour les unions, intersections, ou lors du travail avec des types utilitaires. Les deux fonctionnent bien pour les props de base, donc choisissez une approche et restez cohérent.

Utilisez ReactNode pour le maximum de flexibilité : children: ReactNode. Cela accepte les chaînes, nombres, éléments, fragments et tableaux. Pour des cas plus spécifiques, utilisez ReactElement ou des types d'éléments spécifiques.

Utilisez des fonctions génériques qui préservent les types de props du composant original tout en ajoutant de nouveaux. Le type utilitaire ComponentProps aide à extraire et étendre les types de composants existants sans répétition.

Typez les paramètres de fonction, les types de retour pour les fonctions exportées, et l'état complexe explicitement. Laissez TypeScript inférer les cas simples comme useState avec des primitives ou des gestionnaires d'événements évidents pour réduire la verbosité.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers