Aller au contenu principal

Créer des composants avec props

Isoler la carte de question et le bouton de réponse dans des composants réutilisables, configurés par des props.

Objectifs de la séance​

  • créer deux composants, CarteQuestion et BoutonReponse, chacun dans son fichier ;
  • leur transmettre des informations avec des props typées ;
  • rendre une réponse cliquable avec Pressable et onPress ;
  • changer l'apparence du bouton pendant l'appui (style sous forme de fonction) ;
  • afficher un message dans la console avec console.log.

Petite vérification​

Avant de commencer, vérifiez si vous savez...

  1. dans src/app/index.tsx, recopier l'expression JSX qui affiche le texte de la troisième réponse de la question
  2. exécuter git branch et lire la branche courante
  3. dans index.tsx, citer la propriété de style et sa valeur qui arrondit les coins de la carte
📌 Réponse attendue

TP pour réfléchir et résoudre des problèmes​

Le code de l'écran d'accueil mélange tout : la carte, les réponses, les styles. Votre mission : en extraire deux composants réutilisables, qui vivent dans le dossier src/components/. Vous êtes toujours sur la branche feature/composants.

Étape 1 — Typer les props de CarteQuestion​

Créez le dossier src/components/ et le fichier src/components/CarteQuestion.tsx. La carte reçoit trois informations : le numéro de la question, le total de questions et l'énoncé. On les décrit dans un type appelé Props :


Bonne pratique - Typer toutes les props

Le type Props est le « mode d'emploi » du composant : TypeScript refuse de l'utiliser avec une prop oubliée ou du mauvais type. C'est une documentation toujours à jour.

Étape 2 — Écrire le composant CarteQuestion​

Sous le type, écrivez la fonction du composant. Elle reçoit ses props entre accolades (en les « déstructurant ») et renvoie le JSX de la carte :

Terminez le fichier avec les styles de la carte (déplacés depuis index.tsx) :

const styles = StyleSheet.create({
carte: {
backgroundColor: COULEURS.carte,
borderRadius: 12,
padding: 16,
marginBottom: 16,
},
numero: {
fontSize: 14,
color: COULEURS.texteSecondaire,
marginBottom: 8,
},
enonce: {
fontSize: 18,
fontWeight: "600",
color: COULEURS.texte,
},
});

Bonne pratique - Un composant par fichier

Le nom du fichier (CarteQuestion.tsx) est le même que celui du composant exporté. On retrouve ainsi n'importe quel composant sans le chercher.

Étape 3 — Un bouton cliquable : Pressable et onPress​

Créez src/components/BoutonReponse.tsx. Un <Text> ne réagit pas au toucher : on l'entoure d'un composant Pressable, auquel on transmet la fonction à exécuter lors de l'appui, dans l'attribut onPress. Cette fonction est elle-même une prop du composant :

Le type () => void signifie « une fonction sans paramètre qui ne renvoie rien ».


Bonne pratique - Le composant ne décide pas de l'action

BoutonReponse ne sait pas ce qui se passe quand on clique : il appelle la fonction qu'on lui a donnée. C'est le parent qui décide de l'action, ce qui rend le bouton réutilisable partout.

Étape 4 — Changer l'apparence pendant l'appui​

La ligne style={({ pressed }) => [...]} utilise une particularité de Pressable : son attribut style peut être une fonction qui reçoit l'état du bouton. pressed vaut true tant que le doigt est posé. Ajoutez les styles du bouton, en complétant celui de l'appui :

Dans le tableau [styles.bouton, pressed && styles.boutonAppuye], l'expression pressed && ... vaut false quand le bouton n'est pas appuyé : React Native ignore alors cette valeur, et seul le style de base s'applique.


Bonne pratique - Un retour visuel à chaque appui

Un bouton qui ne réagit pas visuellement donne l'impression de ne pas fonctionner. Un changement d'opacité, même discret, confirme à l'utilisateur que son geste a été pris en compte.

Étape 5 — Utiliser CarteQuestion dans l'écran​

Dans src/app/index.tsx, importez les deux composants. Supprimez de l'écran la View « carte » et son énoncé, ainsi que les styles carte, enonce et reponse devenus inutiles, puis utilisez le nouveau composant :


Bonne pratique - Les props sont en lecture seule

Un composant ne modifie jamais ses props : il les lit seulement. Les données descendent du parent vers l'enfant, jamais l'inverse.

Étape 6 — Utiliser BoutonReponse et réagir au clic​

Remplacez ensuite les quatre <Text> des réponses par quatre <BoutonReponse>. Pour vérifier que le clic fonctionne, ajoutez dans Accueil une petite fonction qui écrit dans la console :

function afficherDansLaConsole(texte: string) {
console.log("Réponse choisie :", texte);
}

Puis complétez la première réponse :

Faites de même pour les indices 1, 2 et 3.


Piège - Ne pas appeler la fonction trop tôt

Écrire onPress={afficherDansLaConsole(texte)} exécute la fonction immédiatement, à l'affichage de l'écran, et non au clic. Pour la retarder, il faut l'envelopper dans une fonction fléchée : onPress={() => afficherDansLaConsole(texte)}.

Bonne pratique - Un nom de fonction qui dit ce qu'elle fait

afficherDansLaConsole décrit exactement son rôle. Plus tard, cette fonction sera remplacée par une vraie action (valider la réponse) : un nom explicite rend ce remplacement évident.

Étape 7 — Tester le clic​

Cliquez sur une réponse dans le navigateur, puis ouvrez la console des outils de développement (F12, onglet Console). Un message Réponse choisie : ... apparaît à chaque clic. Pendant l'appui, le bouton devient semi-transparent. Sur un téléphone avec Expo Go, les messages s'affichent dans le terminal où tourne npx expo start.


Bonne pratique - Supprimer les console.log inutiles

Un console.log est un outil de débogage provisoire. Dans la version finale d'une application, on le retire (nous le ferons dans un tutoriel ultérieur, quand les clics auront une vraie action).

Étape 8 — Enregistrer votre travail​


Bonne pratique - Vérifier les types avant de commiter

Si vous avez oublié une prop en utilisant un composant, npx tsc --noEmit vous le signale tout de suite avec un message clair.

📌 Une solution

Notions théoriques​

Qu'est-ce qu'un composant ?​

Un composant est une fonction qui reçoit des informations et renvoie du JSX. On le choisit pour deux raisons :

  • réutiliser : le même BoutonReponse servira pour les quatre réponses de chaque question ;
  • découper : un écran de 200 lignes devient quelques composants de 30 lignes, bien plus faciles à lire.

Le nom d'un composant commence toujours par une majuscule (CarteQuestion). Sans majuscule, React croirait à une balise ordinaire.

Les props : les paramètres d'un composant​

Les props (properties) sont les informations qu'un composant reçoit de son parent. Elles s'écrivent comme des attributs, et le composant les récupère dans ses paramètres :

// Utilisation (dans le parent)
<CarteQuestion numero={1} total={5} enonce="Quelle est la bonne réponse ?" />

// Définition (dans CarteQuestion.tsx)
export default function CarteQuestion({ numero, total, enonce }: Props) { ... }
  • les accolades dans les paramètres ({ numero, total, enonce }) déstructurent l'objet de props ;
  • le type Props décrit leur forme : une prop manquante ou du mauvais type est signalée par TypeScript ;
  • une valeur texte fixe peut s'écrire avec des guillemets (enonce="..."), tout le reste (nombre, variable, fonction) s'écrit entre accolades ;
  • les props sont en lecture seule : un composant ne les modifie jamais.

Une responsabilité par composant​

CarteQuestion affiche une question, BoutonReponse affiche un bouton : chacun fait une seule chose. Le composant ne connaît ni la banque de questions ni ce que fera le clic ; l'écran, lui, assemble les composants. Cette séparation facilitera l'ajout du score, du chronomètre ou de la navigation.

Pressable et onPress​

Pressable est le composant de base pour tout ce qui doit réagir au toucher. Son attribut onPress reçoit la fonction à exécuter lors de l'appui :

ÉcritureEffet
onPress={onPress}on transmet la fonction reçue en prop
onPress={() => afficherDansLaConsole(texte)}on crée une fonction qui appellera afficherDansLaConsole au clic
onPress={afficherDansLaConsole(texte)}erreur : la fonction est exécutée tout de suite et son résultat transmis

Le style d'un Pressable peut être une fonction qui reçoit { pressed } : on peut ainsi changer l'apparence pendant l'appui. Un tableau de styles [a, b] les combine, le dernier l'emportant en cas de conflit ; une valeur false dans le tableau est ignorée.


Test de mémorisation/compréhension​


Que sont les props d'un composant ?


Pourquoi le nom d'un composant commence-t-il par une majuscule ?


Quelle écriture exécute afficherDansLaConsole(texte) seulement au moment du clic ?


Que signifie le type () => void pour la prop onPress ?


Dans style={({ pressed }) => [styles.bouton, pressed && styles.boutonAppuye]}, que vaut pressed && styles.boutonAppuye quand le bouton n'est pas touché ?


Quel avantage apporte la séparation de BoutonReponse et de l'écran d'accueil ?



Exercices complémentaires​

Exercice A : une prop facultative​

Ajoutez à CarteQuestion une prop facultative theme?: string (le point d'interrogation la rend facultative). Quand elle est fournie, affichez-la sous le numéro de la question dans un <Text>. Vérifiez que l'écran fonctionne avec et sans cette prop.

Exercice B : un composant oublié​

Dans index.tsx, supprimez la prop total de <CarteQuestion ... />. Que signale l'éditeur ? Que répond npx tsc --noEmit ? Remettez la prop.


Ce qu'il faut retenir​

NotionRésumé
ComposantFonction au nom commençant par une majuscule, qui renvoie du JSX ; un composant par fichier.
PropsInformations reçues du parent, décrites par un type Props, en lecture seule.
Déstructuration({ numero, total }: Props) récupère directement chaque prop.
Pressable / onPressRend un élément cliquable ; onPress reçoit la fonction à exécuter.
() => ...Fonction fléchée : retarde l'exécution jusqu'au clic.
style={({ pressed }) => ...}Style qui change pendant l'appui.

Aperçu de la prochaine séance​

Les quatre réponses sont encore écrites une par une. Que se passerait-il si une question avait cinq réponses, ou trois ? Dans la prochaine séance, vous allez remplacer ces quatre blocs par une seule instruction, map(), qui crée un bouton par réponse, et vous découvrirez pourquoi React exige une clé unique pour chaque élément d'une liste.