Mini-projet final : votre premier quiz
Tout ce que vous avez appris, dans une seule application : des questions, des réponses, un score et un écran de résultat.
Objectifs de la séance
- assembler composants,
useState, listes et conditions dans une vraie application ; - gérer proprement la fin de partie (une valeur peut être
undefined) ; - protéger l'application contre le double appui et les dépassements d'indice ;
- réutiliser vos fonctions pures (
calculerPourcentage,appreciation) ; - faire le bilan de vos compétences avant la série Expo.
Petite vérification
Avant de commencer, vérifiez si vous savez...
- dans Expo Snack, avec le code de la séance précédente, appuyer sur Question 1 puis aussitôt sur Question 2 et dire quelle question s'affiche à la fin
- dire à quoi sert le tableau
[id]à la fin deuseEffect - dire ce que fait
controleur.abort()dans la fonction de nettoyage
📌 Réponse attendue
Notions théoriques
Le cahier des charges
Vous allez réaliser, dans un seul fichier App.tsx, un quiz de trois questions :
- l'écran affiche « Question 1 / 3 », l'énoncé et trois réponses ;
- un appui sur une réponse enregistre le choix (une seule fois) et compte un point si elle est correcte ;
- un bouton « Question suivante » apparaît une fois la réponse donnée ;
- après la dernière question, l'écran affiche le score, le pourcentage et l'appréciation, avec un bouton « Rejouer ».
Quels états ?
Chaque donnée qui change doit être dans un état. Pas plus : ce qui se calcule ne se stocke pas.
| État | Type | Rôle |
|---|---|---|
indice | number | numéro de la question en cours (de 0 à 2) |
score | number | nombre de bonnes réponses |
choix | number | null | id de la réponse choisie, ou null |
Ne sont pas des états : la question courante (questions[indice]), le pourcentage et l'appréciation (calculés à chaque rendu).
Les protections à prévoir
Posez-vous les questions de la partie 5 :
- appui deux fois sur une réponse ? La garde
if (choix !== null) return;ignore le second ; - appui sur « Question suivante » sans avoir répondu ? Le bouton n'existe pas tant que
choixestnull, et la fonction a sa propre garde ; - dépassement : après la dernière question,
questions[indice]vautundefined. On déclareconst question: Question | undefined = ..., et TypeScript oblige à gérer ce cas avant d'utiliserquestion.enonce.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Votre mission : construire le quiz complet dans Expo Snack. Remplacez le contenu de App.tsx.
Point de départ (les données et les fonctions pures sont fournies) :
import { useState } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
type Reponse = {
id: number;
texte: string;
estCorrecte: boolean;
};
type Question = {
id: number;
enonce: string;
reponses: Reponse[];
};
const questions: Question[] = [
{
id: 1,
enonce: "Quel mot de passe est le plus robuste ?",
reponses: [
{ id: 11, texte: "123456", estCorrecte: false },
{ id: 12, texte: "MonChienRex", estCorrecte: false },
{ id: 13, texte: "Velo-Rouge-Mange-7-Nuages!", estCorrecte: true },
],
},
{
id: 2,
enonce: "Qu'est-ce que l'hameçonnage ?",
reponses: [
{ id: 21, texte: "Une technique de pêche", estCorrecte: false },
{ id: 22, texte: "Un faux message pour voler vos informations", estCorrecte: true },
{ id: 23, texte: "Un type de pare-feu", estCorrecte: false },
],
},
{
id: 3,
enonce: "À quoi sert un VPN ?",
reponses: [
{ id: 31, texte: "À chiffrer votre connexion", estCorrecte: true },
{ id: 32, texte: "À accélérer le processeur", estCorrecte: false },
{ id: 33, texte: "À supprimer les virus", estCorrecte: false },
],
},
];
function calculerPourcentage(bonnesReponses: number, nombreQuestions: number): number {
if (nombreQuestions <= 0) {
return 0;
}
return Math.round((bonnesReponses / nombreQuestions) * 100);
}
function appreciation(pourcentage: number): string {
if (pourcentage >= 80) {
return "Excellent";
}
if (pourcentage >= 50) {
return "Bien";
}
return "À revoir";
}
Étape 1 — Les trois états
Déclarez l'état indice (numéro de la question en cours, qui commence à 0).
Trois états suffisent. Les ajouts « pratiques » (estTerminee, pourcentage) deviendraient des sources de contradictions s'ils étaient stockés.
Étape 2 — La question courante, qui peut ne plus exister
Récupérez la question courante en déclarant explicitement qu'elle peut être undefined.
Sans cette annotation, TypeScript supposerait que questions[indice] existe toujours. En écrivant | undefined, vous l'obligez à vous rappeler de gérer la fin du quiz.
Étape 3 — Passer à la question suivante
Écrivez la fonction suivante : elle ne fait rien si aucune réponse n'a été donnée, sinon elle avance et efface le choix.
Le bouton « Question suivante » n'est affiché que si choix !== null, mais la fonction vérifie quand même : un code robuste ne compte pas uniquement sur l'interface pour empêcher une action incorrecte.
Étape 4 — L'écran de résultat
Quand question vaut undefined, le quiz est terminé : calculez le pourcentage et affichez le bilan.
calculerPourcentage et appreciation ont été écrites et testées dans la partie 2. Elles fonctionnent ici sans aucune modification : c'est tout l'intérêt de séparer calcul et affichage.
📌 Une solution
Pour aller plus loin
Quand vous aurez terminé, relevez ces défis (ils sont facultatifs) :
- Feedback visuel : colorez en vert la bonne réponse et en rouge la réponse choisie quand elle est fausse (indice :
style={[styles.bouton, condition && styles.bonne]}). - Charger les questions : remplacez le tableau local par un
useEffectqui charge les questions avec un « serveur » simulé (étudié à la séance précédente), avec un étatchargement/succes/erreur. - Chronomètre : affichez le temps écoulé avec
setIntervalet la fonction de nettoyageclearInterval. - Mélange : mélangez l'ordre des questions au début de la partie, sans modifier le tableau d'origine (indice :
[...questions].sort(...)).
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
| Peu d'états | Stocker seulement ce qui change ; calculer le reste à chaque rendu. |
| Valeur possiblement absente | Type | undefined oblige à gérer la fin de liste avant d'utiliser la valeur. |
| Gardes | if (choix !== null) return; : protéger chaque action contre l'appui en double ou hors séquence. |
| Fonctions pures réutilisées | Les calculs écrits dans la partie 2 servent tels quels dans une interface. |
| Mises à jour fonctionnelles | setX((ancien) => ...) dès que le nouvel état dépend de l'ancien. |
Bilan du cours et suite
Félicitations : vous avez terminé le cours ! Voici ce que vous savez désormais faire à la main :
- écrire des fonctions claires, pures et typées ;
- manipuler des objets et des tableaux sans jamais les modifier (
map,filter,...) ; - gérer les erreurs et ne jamais faire confiance à une donnée externe ;
- écrire du code asynchrone robuste :
async/await, annulation, protection contre le double appui et les réponses périmées ; - décrire vos données avec TypeScript ;
- lire et écrire un composant React avec
useStateetuseEffect.
Prochaine étape : la série Expo : un générateur de quiz Cyber. Vous y construirez, dans un vrai projet, une application complète avec navigation, base de données Supabase et classement.