React avec TypeScript

Maintenant que vous connaissez les bases de TypeScript (vues en Séance 1), voyons comment l'utiliser avec React.

TypeScript est obligatoire pour ce cours

Dans ce cours, vous utiliserez TypeScript avec React.

Pourquoi ? Parce que 90% des projets professionnels React utilisent TypeScript. En apprenant React directement avec TypeScript, vous serez prêts pour le monde professionnel et vos futurs stages.

TypeScript est devenu le standard de l'industrie pour les applications React.


Créer un projet React + TypeScript

Avec Vite (recommandé)

bash
# Template React avec TypeScript
npm create vite@latest mon-app -- --template react-ts

cd mon-app
npm install
npm run dev

Le template react-ts configure automatiquement :

  • TypeScript avec compilation rapide
  • Les types pour React (@types/react, @types/react-dom)
  • tsconfig.json pré-configuré pour React

Vérification en temps réel

Avec TypeScript, votre éditeur VS Code détecte les erreurs avant même d'exécuter le code. Plus besoin d'attendre de voir une erreur dans le navigateur !

Structure du projet

Différence principale : Extensions de fichiers

JavaScript (React)       TypeScript (React)
──────────────────       ──────────────────
src/
├── App.jsx          →   ├── App.tsx
├── main.jsx         →   ├── main.tsx
└── Button.jsx       →   └── Button.tsx

.tsx = TypeScript + JSX (au lieu de .jsx)

Fichier de config

Le fichier tsconfig.json est créé automatiquement par Vite. Vous n'avez généralement pas besoin d'y toucher !


Typer les props d'un composant

Props : les paramètres d'un composant

En React, les props (propriétés) sont les données passées à un composant, comme les paramètres d'une fonction. Avec TypeScript, vous définissez explicitement quelles props un composant attend et leur type.

Composant simple

JavaScript :

tsx
function Welcome({ name }) {
  return <h1>Bonjour, {name} !</h1>;
}

TypeScript - Méthode 1 (type inline) :

tsx
function Welcome({ name }: { name: string }) {
  return <h1>Bonjour, {name} !</h1>;
}

TypeScript - Méthode 2 (interface, recommandé) :

tsx
interface WelcomeProps {
  name: string;
}

function Welcome({ name }: WelcomeProps) {
  return <h1>Bonjour, {name} !</h1>;
}

Quelle méthode choisir ?

Utilisez interface pour les props !

C'est plus lisible et réutilisable. Les types inline conviennent pour les composants très simples.

Quelle est la méthode recommandée pour typer les props d'un composant React ?

Props avec plusieurs types

tsx
interface UserCardProps {
  name: string;
  age: number;
  email: string;
  isActive: boolean;
  avatar?: string;  // ? = optionnel
}

function UserCard({ name, age, email, isActive, avatar }: UserCardProps) {
  return (
    <div className="user-card">
      <h2>{name}</h2>
      <p>Âge : {age} ans</p>
      <p>Email : {email}</p>
      <p>Statut : {isActive ? '🟢 Actif' : '🔴 Inactif'}</p>
      {avatar && <img src={avatar} alt={name} />}
    </div>
  );
}

// Utilisation
<UserCard
  name="Alice"
  age={25}
  email="alice@example.com"
  isActive={true}
  // avatar est optionnel, on peut l'omettre
/>

Props avec union types

tsx
interface ButtonProps {
  label: string;
  variant: 'primary' | 'secondary' | 'danger';  // Seulement ces 3 valeurs
  size: 'small' | 'medium' | 'large';
}

function Button({ label, variant, size }: ButtonProps) {
  return (
    <button className={`btn-${variant} btn-${size}`}>
      {label}
    </button>
  );
}

// Utilisation
<Button label="Cliquer" variant="primary" size="medium" />
// <Button label="Test" variant="warning" /> // ❌ Erreur: 'warning' n'est pas valide

Props avec valeurs par défaut

tsx
interface ButtonProps {
  label: string;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

function Button({
  label,
  variant = 'primary',  // Valeur par défaut
  disabled = false
}: ButtonProps) {
  return (
    <button className={`btn-${variant}`} disabled={disabled}>
      {label}
    </button>
  );
}

Props avec fonctions

Les fonctions passées en props se tapent avec leur signature :

tsx
interface SearchBarProps {
  onSearch: (query: string) => void;  // Fonction avec paramètre
  onClear: () => void;                 // Fonction sans paramètre
}

function SearchBar({ onSearch, onClear }: SearchBarProps) {
  const [value, setValue] = useState('');

  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <button onClick={() => onSearch(value)}>🔍</button>
      <button onClick={onClear}>✖️</button>
    </div>
  );
}

Typer children

Le type pour le contenu JSX enfant est React.ReactNode :

tsx
interface CardProps {
  title: string;
  children: React.ReactNode;
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="content">
        {children}
      </div>
    </div>
  );
}

// Utilisation
<Card title="Mon titre">
  <p>Contenu de la carte</p>
  <button>Action</button>
</Card>

React.ReactNode

React.ReactNode accepte :

  • Chaînes de caractères
  • Nombres
  • JSX (éléments React)
  • Tableaux
  • null ou undefined
  • Fragments

C'est le type le plus permissif pour children.


Typer les événements

Pour les événements React, TypeScript fournit des types spécifiques :

tsx
function LoginForm() {
  // Soumission de formulaire
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Formulaire soumis');
  };

  // Changement d'input
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log('Valeur:', e.target.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} placeholder="Email" />
      <button type="submit">Connexion</button>
    </form>
  );
}

Laisser TypeScript inférer

Pour les gestionnaires inline, vous n'avez pas besoin de typer :

tsx
<input onChange={(e) => console.log(e.target.value)} />
<button onClick={(e) => console.log('Cliqué')}>Cliquer</button>

TypeScript devine automatiquement le type de e.


Typer useState

useState : le hook d'état

useState est le hook React qui permet de gérer l'état local d'un composant. TypeScript peut souvent deviner le type automatiquement, mais parfois vous devez l'indiquer explicitement.

Inférence automatique

tsx
import { useState } from 'react';

function Counter() {
  // TypeScript devine que count est un number
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

Type explicite (quand nécessaire)

tsx
interface User {
  id: number;
  name: string;
  email: string;
}

function UserProfile() {
  // State qui peut être User ou null
  const [user, setUser] = useState<User | null>(null);

  // Tableau vide au départ
  const [users, setUsers] = useState<User[]>([]);

  return (
    <div>
      {user ? (
        <p>{user.name} - {user.email}</p>
      ) : (
        <p>Aucun utilisateur</p>
      )}
    </div>
  );
}

État avec objet complexe

tsx
interface FormData {
  name: string;
  email: string;
  age: number;
  subscribe: boolean;
}

function Form() {
  const [formData, setFormData] = useState<FormData>({
    name: '',
    email: '',
    age: 0,
    subscribe: false
  });

  const handleChange = (field: keyof FormData, value: string | number | boolean) => {
    setFormData({
      ...formData,
      [field]: value
    });
  };

  return (
    <form>
      <input
        value={formData.name}
        onChange={(e) => handleChange('name', e.target.value)}
      />
      {/* ... */}
    </form>
  );
}

Quand devez-vous spécifier explicitement le type dans useState ?


Erreurs courantes

Erreur : Property 'X' is missing

tsx
// ❌ Prop requise manquante
<Button label="Cliquer" />

// ✅ Ajoutez la prop ou rendez-la optionnelle (?)
<Button label="Cliquer" onClick={() => {}} />

Erreur : Type 'string' is not assignable to type 'number'

tsx
// ❌ Type incorrect
<User age="25" />

// ✅ Utilisez le bon type
<User age={25} />

Erreur : Object is possibly 'null'

tsx
// ❌ Accès sans vérification
const name = user.name;

// ✅ Optional chaining
const name = user?.name;

Interface vs Type

Pour les props React, utilisez interface (plus lisible) :

tsx
// ✅ Recommandé pour les props
interface ButtonProps {
  label: string;
  onClick: () => void;
}

// ✅ Pour les unions
type Status = 'loading' | 'success' | 'error';

Vous avez vu les détails en Séance 1.


Résumé

Ce que vous savez maintenant

Créer un projet React + TypeScript :

bash
npm create vite@latest mon-app -- --template react-ts

Typer les props avec interface :

tsx
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;  // Optionnel avec ?
}

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

Types React essentiels :

  • React.ReactNode → Pour children
  • React.FormEvent → Pour onSubmit
  • React.ChangeEvent<HTMLInputElement> → Pour onChange

useState avec types :

tsx
const [user, setUser] = useState<User | null>(null);
const [users, setUsers] = useState<User[]>([]);

Conventions :

  • interface pour les props
  • type pour les unions ('loading' | 'success')
  • Fichiers React : .tsx (TypeScript + JSX)

Ressources complémentaires


Prochaine étape : Passez aux exercices pour mettre en pratique tous les concepts React !