Cours JavaScript et React · Module 12: React et TypeScript
Typer les props - interfaces et propriétés optionnelles
Dans cette leçon5
Une carte de membre d'équipage fonctionne très bien, jusqu'au jour où quelqu'un oublie de passer le grade ou donne l'expérience sous forme de texte "quinze". Les props sont la communication cosmique entre les composants. TypeScript te permet de définir précisément quelles données peuvent être transmises - c'est comme un protocole de communication sur un vaisseau spatial : un message qui ne respecte pas le protocole n'est même pas envoyé.
Interfaces pour les props
Les interfaces sont la façon la plus courante de définir les types des props en React. Le nom de l'interface se termine généralement par Props, et chaque champ correspond à un attribut que le parent passe en JSX. D'abord, le protocole lui-même :
1// Une interface définit la "forme" des données
2interface CrewMemberProps {
3 name: string;
4 rank: string;
5 experience: number;
6 specialization: string;
7}Maintenant, on l'attache au paramètre du composant, comme dans la leçon précédente, et on montre l'utilisation :
1function CrewMember({ name, rank, experience, specialization }: CrewMemberProps) {
2 return (
3 <div>
4 <h3>{name}</h3>
5 <p>Rank: {rank}</p>
6 <p>Years of experience: {experience}</p>
7 <p>Specialization: {specialization}</p>
8 </div>
9 );
10}
11
12// Utilisation - TypeScript vérifie toutes les props
13<CrewMember
14 name="Nova"
15 rank="Captain"
16 experience={15}
17 specialization="Navigation"
18/>Remarque experience={15} : les nombres se passent entre accolades, le texte entre guillemets. Si tu oubliais rank ou écrivais experience="15", l'éditeur soulignerait l'erreur avant toute exécution. L'appel en JSX ressemble exactement à du JavaScript - le protocole agit en coulisses.
Props optionnelles
Toutes les props ne sont pas obligatoires. Utilise le signe ? après le nom du champ pour marquer une prop comme optionnelle. Le type d'un tel champ est en pratique "une valeur ou undefined". L'exemple utilise aussi un type union, un type composé de plusieurs valeurs autorisées reliées par |, ainsi que Date, le type de date intégré.
1interface MissionProps {
2 title: string; // obligatoire
3 description: string; // obligatoire
4 priority?: "low" | "medium" | "high"; // optionnelle
5 deadline?: Date; // optionnelle
6 assignedCrew?: string[]; // optionnelle
7}Le champ priority n'accepte que l'une des trois chaînes - la faute "hihg" est refusée. Comment définir une valeur par défaut ? Le plus simple est de le faire dans la déstructuration du paramètre, avec = :
1function MissionCard({ title, description, priority = "medium", deadline, assignedCrew }: MissionProps) {
2 return (
3 <div>
4 <h3>{title}</h3>
5 <p>{description}</p>
6 <span>Priority: {priority}</span>
7 {deadline && <p>Deadline: {deadline.toLocaleDateString()}</p>}
8 {assignedCrew && (
9 <ul>
10 {assignedCrew.map(member => <li key={member}>{member}</li>)}
11 </ul>
12 )}
13 </div>
14 );
15}Dans le composant, priority n'inclut plus undefined, car la valeur par défaut comble le vide. deadline et assignedCrew peuvent manquer, donc on les vérifie avec && avant d'appeler une méthode dessus - TypeScript ne le permettrait pas sans cette vérification. Note pour les lecteurs d'anciens guides : defaultProps pour les composants fonctionnels a été supprimé dans React 19, donc définis les valeurs par défaut uniquement dans la déstructuration.
Les deux appels ci-dessous sont valides :
1// Peut être utilisé sans les props optionnelles
2<MissionCard title="Recon" description="Sector 7 reconnaissance" />
3
4// Ou avec toutes
5<MissionCard
6 title="Recon"
7 description="Sector 7 reconnaissance"
8 priority="high"
9 assignedCrew={["Nova", "Ra"]}
10/>Omettre title ou description serait une erreur, mais tu peux ajouter les champs optionnels dans n'importe quelle combinaison.
Typer children
La prop children est une prop spéciale en React : elle reçoit tout ce que tu places entre la balise ouvrante et la balise fermante du composant. Le type React.ReactNode couvre tout ce que React sait afficher : éléments JSX, texte, nombres, null et tableaux de ces valeurs.
1// React.ReactNode - accepte tout ce que React peut afficher
2interface PanelProps {
3 title: string;
4 children: React.ReactNode;
5}
6
7function Panel({ title, children }: PanelProps) {
8 return (
9 <div className="panel">
10 <h2>{title}</h2>
11 <div className="content">{children}</div>
12 </div>
13 );
14}
15
16// Utilisation
17<Panel title="Mission Data">
18 <p>Status: active</p>
19 <MissionCard title="Recon" description="..." />
20</Panel>Le panneau ne sait pas ce qu'il va recevoir, et il n'en a pas besoin - il accepte un paragraphe, une autre carte ou du texte simple. Si tu voulais n'accepter que des éléments JSX, react.dev indique le type plus étroit React.ReactElement, mais pour les panneaux et les conteneurs React.ReactNode est le bon choix.
Type vs interface
Tu peux aussi utiliser type au lieu de interface. Le mot-clé type crée un alias, un nouveau nom pour n'importe quel type - y compris un type union, ce qu'une interface ne sait pas exprimer.
1// Alias de type - alternative à interface
2type ButtonVariant = "primary" | "secondary" | "danger";
3
4type ButtonProps = {
5 label: string;
6 variant: ButtonVariant;
7 disabled?: boolean;
8 onClick: () => void;
9};() => void est le type d'une fonction qui ne prend rien et ne renvoie rien - c'est ainsi qu'on décrit les callbacks. Une interface, elle, peut être étendue avec extends, en héritant de tous les champs du protocole de base :
1// Interface - peut être étendue
2interface BaseProps {
3 id: string;
4 className?: string;
5}
6
7interface ShipProps extends BaseProps {
8 name: string;
9 speed: number;
10}
11
12// ShipProps possède : id, className, name, speedMa recommandation : interface pour les props d'objets, type pour les unions et les alias. Le plus important reste la cohérence dans tout le projet.
Étendre les interfaces
Les interfaces peuvent être combinées et étendues pour créer des structures plus complexes. Un champ peut avoir une autre interface pour type, et plusieurs interfaces peuvent étendre la même base :
1interface Coordinates {
2 x: number;
3 y: number;
4 z: number;
5}
6
7interface SpaceObject {
8 name: string;
9 coordinates: Coordinates;
10}
11
12interface Planet extends SpaceObject {
13 radius: number;
14 habitable: boolean;
15 atmosphere: string[];
16}
17
18interface Star extends SpaceObject {
19 temperature: number;
20 luminosity: number;
21 spectralClass: string;
22}Planet et Star partagent name et coordinates, et chacun ajoute ses propres champs. Une modification de SpaceObject touche immédiatement les deux. Ces hiérarchies reviendront dans la leçon sur les unions discriminées, où nous apprendrons à un composant à reconnaître l'objet qu'il a reçu.
Code de cette leçon : App.tsx
1import React from 'react';
2
3// Interfaces avec des props optionnelles
4interface Coordinates {
5 x: number;
6 y: number;
7 z: number;
8}
9
10interface SpaceObjectProps {
11 name: string;
12 type: string;
13 coordinates: Coordinates;
14 description?: string; // optionnel
15 dangerLevel?: 1 | 2 | 3 | 4 | 5; // optionnel
16 tags?: string[]; // optionnel
17}
18
19// Composant avec props optionnelles et valeurs par défaut
20function SpaceObject({
21 name,
22 type,
23 coordinates,
24 description = "Aucune description",
25 dangerLevel = 1,
26 tags = []
27}: SpaceObjectProps) {
28 const dangerColors = ["#22c55e", "#84cc16", "#eab308", "#f97316", "#ef4444"];
29
30 return (
31 <div className="object-card">
32 <h3>{name}</h3>
33 <span className="type-badge">{type}</span>
34 <p>{description}</p>
35 <p className="coords">
36 Position : ({coordinates.x}, {coordinates.y}, {coordinates.z})
37 </p>
38 <div className="danger" style={{ color: dangerColors[dangerLevel - 1] }}>
39 Danger : {"*".repeat(dangerLevel)}{"*".repeat(0)}
40 </div>
41 {tags.length > 0 && (
42 <div className="tags">
43 {tags.map(tag => <span key={tag} className="tag">{tag}</span>)}
44 </div>
45 )}
46 </div>
47 );
48}
49
50// Composant avec children
51interface PanelProps {
52 title: string;
53 children: React.ReactNode;
54}
55
56function Panel({ title, children }: PanelProps) {
57 return (
58 <div className="panel">
59 <h2>{title}</h2>
60 <div className="panel-content">{children}</div>
61 </div>
62 );
63}
64
65function App() {
66 return (
67 <div className="app">
68 <h1>Carte cosmique</h1>
69 <Panel title="Objets détectés">
70 <SpaceObject
71 name="Kepler-442b"
72 type="Planète"
73 coordinates={{ x: 1206, y: 340, z: -89 }}
74 description="Super-Terre potentiellement habitable"
75 dangerLevel={1}
76 tags={["habitable", "rocheuse"]}
77 />
78 <SpaceObject
79 name="Betelgeuse"
80 type="Étoile"
81 coordinates={{ x: 700, y: -200, z: 45 }}
82 dangerLevel={5}
83 />
84 <SpaceObject
85 name="99942 Apophis"
86 type="Astéroïde"
87 coordinates={{ x: 0.1, y: 0.02, z: 0 }}
88 description="Astéroïde géocroiseur"
89 dangerLevel={4}
90 tags={["NEO", "dangereux"]}
91 />
92 </Panel>
93 </div>
94 );
95}
96
97export default App;Retiens : l'interface des props est le protocole de communication - ce qui ne respecte pas le protocole ne passe pas le sas.
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Comment marque-t-on une prop comme optionnelle dans une interface TypeScript ?
2. Quel type TypeScript faut-il utiliser pour la prop children afin d'accepter n'importe quel contenu React ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Typer les props avec une interface et children
- Tri horizontal
Ordonne la syntaxe pour typer les props d'un composant à l'aide d'une interface :