Article original : How to Build a Memory Card Game Using React
Récemment, en regardant ma fille 👩🏻🦰 jouer à des jeux de mémoire gratuits sur sa tablette, j'ai remarqué qu'elle avait du mal avec un nombre écrasant de publicités et de bannières pop-up ennuyeuses.
Cela m'a inspiré à créer un jeu similaire pour elle. Comme elle est actuellement passionnée par les anime, j'ai décidé de créer le jeu en utilisant des images mignonnes de style anime.
Dans cet article, je vais vous guider à travers le processus de création du jeu pour vous-même ou pour vos enfants 🎮.
Nous commencerons par explorer les fonctionnalités du jeu, puis nous couvrirons la pile technologique et la structure du projet, toutes deux étant simples. Enfin, nous discuterons des optimisations et de la garantie d'un gameplay fluide sur les appareils mobiles 📱.
Si vous souhaitez passer la lecture, ici 💁 est le dépôt GitHub 👍. Et ici vous pouvez voir la démo en direct.
Table des matières
Description du projet
Dans ce tutoriel, nous allons construire un jeu de mémoire stimulant avec React qui teste vos capacités de rappel. Votre objectif est de cliquer sur des images d'anime uniques sans cliquer deux fois sur la même. Chaque clic unique vous rapporte des points, mais attention, cliquer deux fois sur une image réinitialise votre progression.

Fonctionnalités du jeu:
🏮 Un gameplay dynamique qui met au défi votre mémoire
🔍 Les cartes se mélangent après chaque clic pour augmenter la difficulté
🏆 Suivi des scores avec persistance du meilleur score
😺 Des images d'anime adorables de The Nekosia API
✨ Des transitions de chargement fluides et des animations
📱 Un design réactif pour tous les appareils
🎨 Une interface utilisateur propre et moderne
Le jeu vous aidera à tester vos compétences en mémoire tout en profitant de jolies images d'anime. Pouvez-vous obtenir le score parfait ?
Comment jouer
Cliquez sur n'importe quelle carte pour commencer
Rappelez-vous quelles cartes vous avez cliquées
Essayez de cliquer sur toutes les cartes exactement une fois
Regardez votre score augmenter avec chaque sélection unique
Continuez à jouer pour essayer de battre votre meilleur score
La pile technologique
Voici une liste des principales technologies que nous allons utiliser :
NPM – Un gestionnaire de paquets pour JavaScript qui aide à gérer les dépendances et les scripts du projet.
Vite – Un outil de construction qui fournit un environnement de développement rapide, particulièrement optimisé pour les projets web modernes.
React – Une bibliothèque JavaScript populaire pour construire des interfaces utilisateur, permettant un rendu efficace et une gestion d'état.
CSS Modules – Une solution de style qui limite la portée du CSS aux composants individuels, empêchant les conflits de style et assurant la maintenabilité.
Construisons le jeu
À partir de ce point, je vais vous guider à travers le processus que j'ai suivi lors de la construction de ce jeu.
Structure et architecture du projet
Lors de la construction de ce jeu de mémoire, j'ai soigneusement organisé la base de code pour assurer la maintenabilité, la scalabilité et une séparation claire des préoccupations. Explorons la structure et la raison derrière chaque décision :

Architecture basée sur les composants
J'ai choisi une architecture basée sur les composants pour plusieurs raisons :
Modularité : Chaque composant est autonome avec sa propre logique et ses styles
Réutilisabilité : Les composants comme
CardetLoaderpeuvent être réutilisés dans toute l'applicationMaintenabilité : Plus facile à déboguer et à modifier les composants individuels
Testabilité : Les composants peuvent être testés de manière isolée
Organisation des composants
- Composant Card
Séparé dans son propre répertoire car c'est un élément central du jeu
Contient à la fois les modules JSX et SCSS pour l'encapsulation
Gère le rendu des cartes individuelles, les états de chargement et les événements de clic
- Composant CardsGrid
Gère la disposition du plateau de jeu
Gère le mélange et la distribution des cartes
Contrôle la disposition de la grille réactive pour différentes tailles d'écran
- Composant Loader
Indicateur de chargement réutilisable
Améliore l'expérience utilisateur pendant le chargement des images
Peut être utilisé par n'importe quel composant ayant besoin d'états de chargement
- Composants Header/Footer/Subtitle
Composants structurels pour la disposition de l'application
Header affiche le titre du jeu et les scores
Footer montre les informations de copyright et de version
Subtitle fournit les instructions du jeu
Approche des modules CSS
J'ai utilisé les modules CSS (fichiers .module.scss) pour plusieurs avantages :
Styling à portée limitée : Empêche les fuites de style entre les composants
Collisions de noms : Génère automatiquement des noms de classe uniques
Maintenabilité : Les styles sont co-localisés avec leurs composants
Fonctionnalités SCSS : Utilise les fonctionnalités SCSS tout en gardant les styles modulaires
Hooks personnalisés
Le répertoire hooks contient des hooks personnalisés comme useFetch :
Séparation des préoccupations : Isole la logique de récupération des données
Réutilisabilité : Peut être utilisé par n'importe quel composant ayant besoin de données d'image
Gestion d'état : Gère les états de chargement, d'erreur et de données
Performance : Implémente des optimisations comme le contrôle de la taille des images
Fichiers de niveau racine
App.jsx :
Agit comme point d'entrée de l'application
Gère l'état global et le routage (si nécessaire)
Coordonne la composition des composants
Gère les dispositions de haut niveau
Considérations de performance
La structure supporte les optimisations de performance :
Fractionnement du code : Les composants peuvent être chargés de manière paresseuse si nécessaire
Mémoisation : Les composants peuvent être mémoisés efficacement
Chargement des styles : Les modules CSS permettent un chargement efficace des styles
Gestion des actifs : Les images et les ressources sont correctement organisées
Scalabilité
Cette structure permet une mise à l'échelle facile :
De nouvelles fonctionnalités peuvent être ajoutées sous forme de nouveaux composants
Des hooks supplémentaires peuvent être créés pour de nouvelles fonctionnalités
Les styles restent maintenables à mesure que l'application grandit
Les tests peuvent être implémentés à tout niveau
Expérience de développement
La structure améliore l'expérience du développeur :
Organisation claire des fichiers
Emplacements intuitifs des composants
Facile à trouver et à modifier des fonctionnalités spécifiques
Supporte une collaboration efficace
Cette architecture s'est avérée particulièrement précieuse lors de l'optimisation du jeu pour une utilisation sur tablette, car elle m'a permis de :
Identifier et optimiser facilement les goulots d'étranglement de performance
Ajouter des styles spécifiques aux tablettes sans affecter les autres appareils
Implémenter des états de chargement pour une meilleure expérience mobile
Maintenir une séparation claire entre la logique du jeu et les composants UI
D'accord, maintenant commençons à coder.
Guide de construction étape par étape
1. Configuration du projet
Configurer l'environnement de développement
Pour commencer avec un projet React propre, ouvrez votre application de terminal et exécutez les commandes suivantes (vous pouvez nommer votre dossier de projet comme vous le souhaitez - dans mon cas, le nom est 'memory-card') :
npm create vite@latest memory-card -- --template react
cd memory-card
npm install
Installer les dépendances requises
Les seules dépendances que nous utiliserons dans ce projet sont le package de hooks de UI.dev (au fait, ici vous pouvez trouver un article bien expliqué sur le fonctionnement du rendu dans React).
L'autre dépendance est le célèbre préprocesseur CSS, SASS, dont nous aurons besoin pour pouvoir écrire nos modules CSS en SASS au lieu de CSS régulier.
npm install @uidotdev/usehooks sass
Configurer Vite et les paramètres du projet
Lors de la configuration de notre projet, nous devons apporter quelques ajustements de configuration spécifiques pour gérer les avertissements SASS et améliorer notre expérience de développement. Voici comment vous pouvez configurer Vitest :
// vitest.config.js
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/setupTests.js'],
css: {
modules: {
classNameStrategy: 'non-scoped'
}
},
preprocessors: {
'**/*.scss': 'sass'
},
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'src/setupTests.js',
'src/main.jsx',
'src/vite-env.d.ts',
],
},
},
css: {
preprocessorOptions: {
scss: {
quietDeps: true, // Supprime les avertissements de dépendance SASS
charset: false // Empêche l'avertissement de charset dans les versions récentes de SASS
}
}
}
});
Gardez à l'esprit que la plupart de ces configurations sont générées automatiquement pour vous lorsque vous créez le projet avec Vite. Voici ce qui se passe :
Configuration SASS :
quietDeps: true: Cela supprime les avertissements concernant les dépendances obsolètes dans les modules SASS. Particulièrement utile lorsque vous travaillez avec des fichiers SASS/SCSS tiers.charset: false: Empêche l'avertissement "@charset" qui apparaît dans les nouvelles versions de SASS lorsque vous utilisez des caractères spéciaux dans vos feuilles de style.
Configuration des tests :
globals: true: Rend les fonctions de test globalement disponibles dans les fichiers de testenvironment: 'jsdom': Fournit un environnement DOM pour les testssetupFiles: Pointe vers notre fichier de configuration de test
Ces configurations aident à créer une expérience de développement plus propre en supprimant les messages d'avertissement inutiles dans la console, en configurant les paramètres de l'environnement de test et en garantissant que le traitement SASS/SCSS fonctionne en douceur.
Vous pourriez voir des avertissements dans votre console sans ces configurations lorsque :
Vous utilisez des fonctionnalités SASS/SCSS ou importez des fichiers SASS
Vous exécutez des tests qui nécessitent une manipulation du DOM
Vous utilisez des caractères spéciaux dans vos feuilles de style
2. Construction des composants
Créer le composant Card
Tout d'abord, créons notre composant de carte de base qui affichera des images individuelles :
// src/components/Card/Card.jsx
import React, { useState, useCallback } from "react";
import Loader from "../Loader";
import styles from "./Card.module.scss";
const Card = React.memo(function Card({ imgUrl, imageId, categoryName, processTurn }) {
const [isLoading, setIsLoading] = useState(true);
const handleImageLoad = useCallback(() => {
setIsLoading(false);
}, []);
const handleClick = useCallback(() => {
processTurn(imageId);
}, [processTurn, imageId]);
return (
<div className={styles.container} onClick={handleClick}>
{isLoading && (
<div className={styles.loaderContainer}>
<Loader message="Chargement..." />
</div>
)}
<img
src={imgUrl}
alt={categoryName}
onLoad={handleImageLoad}
className={`${styles.image} ${isLoading ? styles.hidden : ''}`}
/>
</div>
);
});
export default Card;
Le composant Card est un élément fondamental de notre jeu. Il est responsable de l'affichage des images individuelles et de la gestion des interactions des joueurs. Décomposons son implémentation :
Détail des props :
image: (string)L'URL de l'image à afficher qui est reçue de notre service API.
Elle est utilisée directement dans l'attribut src de la balise img.
id: (string)Identifiant unique pour chaque carte qui est crucial pour suivre les cartes qui ont été cliquées.
Il est passé à la fonction de rappel
processTurnlorsqu'une carte est cliquée.
category: (string)Décrit le type d'image (par exemple, "anime", "neko"), et il est utilisé dans l'attribut alt pour une meilleure accessibilité.
Il aide avec le SEO et les lecteurs d'écran.
processTurn: (function)Fonction de rappel passée depuis le composant parent qui gère la logique du jeu lorsqu'une carte est cliquée.
Elle gère également les mises à jour des scores et les changements d'état du jeu et détermine si une carte a été cliquée auparavant.
isLoading: (boolean)Contrôle si un état de chargement doit être affiché. Lorsqu'il est vrai, il affiche un composant Loader au lieu de l'image.
Il améliore l'expérience utilisateur pendant le chargement de l'image.
Style du composant :
// src/components/Card/Card.module.scss
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 20px;
font-size: 30px;
text-align: center;
min-height: 200px;
position: relative;
cursor: pointer;
transition: transform 0.2s ease;
&:hover {
transform: scale(1.02);
}
.image {
width: 10rem;
height: auto;
opacity: 1;
transition: opacity 0.3s ease;
&.hidden {
opacity: 0;
}
}
.loaderContainer {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
Utilisation dans le composant :
<Card
key={getKey()}
imgUrl={item?.image?.original?.url || ""}
imageId={item?.id}
categoryName={item?.category}
processTurn={(imageId) => processTurn(imageId)}
/>
Fonctionnalités clés :
Optimisation des performances :
Utilise
React.memopour éviter les re-rendus inutilesImplémente
useCallbackpour les gestionnaires d'événementsGère l'état de chargement en interne pour une meilleure UX
Gestion de l'état de chargement :
État interne
isLoadingpour suivre le chargement de l'imageAffiche un composant Loader avec un message pendant le chargement
Cache l'image jusqu'à ce qu'elle soit complètement chargée en utilisant des classes CSS
Gestion des événements :
handleImageLoad: Gère la transition de l'état de chargementhandleClick: Traite les mouvements du joueur via la fonction de rappelprocessTurn
Construire le composant CardsGrid
C'est notre composant principal de jeu qui gère l'état du jeu, la logique des scores et les interactions des cartes. Décomposons son implémentation :
// src/components/CardsGrid/CardsGrid.jsx
import React, { useState, useEffect } from "react";
import { useLocalStorage } from "@uidotdev/usehooks";
import Card from "../Card";
import Loader from "../Loader";
import styles from "./CardsGrid.module.scss";
import useFetch from "../../hooks/useFetch";
function CardsGrid(data) {
// Gestion de l'état
const [images, setImages] = useState(data?.data?.images || []);
const [clickedImages, setClickedImages] = useLocalStorage("clickedImages", []);
const [score, setScore] = useLocalStorage("score", 0);
const [bestScore, setBestScore] = useLocalStorage("bestScore", 0);
const [isLoading, setIsLoading] = useState(!data?.data?.images?.length);
// Hook personnalisé pour récupérer les images
const { data: fetchedData, fetchData, error } = useFetch();
// Mettre à jour les images lorsque de nouvelles données sont récupérées
useEffect(() => {
if (fetchedData?.images) {
setImages(fetchedData.images);
setIsLoading(false);
// Réinitialiser les images cliquées lorsqu'un nouveau lot est chargé
setClickedImages([]);
}
}, [fetchedData]);
// Fonction auxiliaire pour mettre à jour le meilleur score
function updateBestScore(currentScore) {
if (currentScore > bestScore) {
setBestScore(currentScore);
}
}
// Logique principale du jeu
function processTurn(imageId) {
const newClickedImages = [...clickedImages, imageId];
setClickedImages(newClickedImages);
// Si on clique deux fois sur la même image, tout réinitialiser
if (clickedImages.includes(imageId)) {
// Mettre à jour le meilleur score si nécessaire
updateBestScore(score);
setClickedImages([]);
setScore(0);
} else {
// Gérer la sélection réussie de la carte
const newScore = score + 1;
setScore(newScore);
// Vérifier le score parfait (toutes les cartes cliquées une fois)
if (newClickedImages.length === images.length) {
updateBestScore(newScore);
fetchData();
setClickedImages([]);
} else {
// Mélanger les images
const shuffled = [...images].sort(() => Math.random() - 0.5);
setImages(shuffled);
}
}
}
if (error) {
return <p>Échec de la récupération des données</p>;
}
if (isLoading) {
return <Loader message="Chargement des nouvelles images..." />;
}
return (
<div className={styles.container}>
{images.map((item) => (
<Card
key={getKey()}
imgUrl={item?.image?.original?.url || ""}
imageId={item?.id}
categoryName={item?.category}
processTurn={(imageId) => processTurn(imageId)}
/>
))}
</div>
);
}
export default React.memo(CardsGrid);
Style du composant :
.container {
display: grid;
gap: 1rem 1rem;
grid-template-columns: auto; /* Par défaut : une colonne pour mobile-first */
background-color: #2196f3;
padding: 0.7rem;
cursor: pointer;
}
@media (min-width: 481px) {
.container {
grid-template-columns: auto auto; /* Deux colonnes pour les tablettes et plus */
}
}
@media (min-width: 769px) {
.container {
grid-template-columns: auto auto auto; /* Trois colonnes pour les ordinateurs de bureau et plus grands */
}
}
Détail des fonctionnalités clés :
Gestion de l'état :
Utilise
useStatepour l'état au niveau du composantImplémente
useLocalStoragepour les données de jeu persistantes :clickedImages: Suivi des cartes qui ont été cliquéesscore: Score actuel du jeubestScore: Meilleur score atteint
Gère l'état de chargement pour la récupération des images
Mélange les cartes
Logique du jeu :
processTurn: Gère les mouvements du joueurSuivi des clics en double
Mise à jour des scores
Gestion des scénarios de score parfait
updateBestScore: Met à jour le meilleur score lorsque nécessaireRécupère automatiquement de nouvelles images lorsqu'une manche est terminée
Récupération des données :
Utilise le hook personnalisé
useFetchpour les données d'imageGère les états de chargement et d'erreur
Met à jour les images lorsque de nouvelles données sont récupérées
Optimisation des performances :
Composant enveloppé dans
React.memoMises à jour d'état efficaces
Disposition de grille réactive
Persistance :
L'état du jeu persiste à travers les rechargements de page
Suivi du meilleur score
Sauvegarde de la progression actuelle du jeu
Exemple d'utilisation :
...
...
function App() {
const { data, loading, error } = useFetch();
if (loading) return <Loader />;
if (error) return <p>Erreur : {error}</p>;
return (
<div className={styles.container}>
<Header />
<Subtitle />
<CardsGrid data={data} />
<Footer />
</div>
);
}
export default App;
Le composant CardsGrid sert de cœur à notre jeu de mémoire, gérant :
L'état et la logique du jeu
Le suivi des scores
Les interactions des cartes
Le chargement et l'affichage des images
La disposition réactive
La persistance des données
Cette implémentation offre une expérience de jeu fluide tout en maintenant la lisibilité et la maintenabilité du code grâce à une séparation claire des préoccupations et une gestion appropriée de l'état.
3. Implémentation de la couche API
Notre jeu utilise une couche API robuste avec plusieurs options de secours pour garantir une livraison fiable des images. Implémentons chaque service et le mécanisme de secours.
Configurer le service API principal :
// src/services/api/nekosiaApi.js
const NEKOSIA_API_URL = "https://api.nekosia.cat/api/v1/images/catgirl";
export async function fetchNekosiaImages() {
const response = await fetch(
`${NEKOSIA_API_URL}?count=21&additionalTags=white-hair,uniform&blacklistedTags=short-hair,sad,maid&width=300`
);
if (!response.ok) {
throw new Error(`Erreur de l'API Nekosia : ${response.status}`);
}
const result = await response.json();
if (!result.images || !Array.isArray(result.images)) {
throw new Error('Format de réponse invalide de l\'API Nekosia');
}
const validImages = result.images.filter(item => item?.image?.original?.url);
if (validImages.length === 0) {
throw new Error('Aucune image valide reçue de l\'API Nekosia');
}
return { ...result, images: validImages };
}
Créer le premier service API de secours :
// src/services/api/nekosBestApi.js
const NEKOS_BEST_API_URL = "https://nekos.best/api/v2/neko?amount=21";
export async function fetchNekosBestImages() {
const response = await fetch(NEKOS_BEST_API_URL, {
method: "GET",
mode: "no-cors"
});
if (!response.ok) {
throw new Error(`Erreur de l\'API Nekos Best : ${response.status}`);
}
const result = await response.json();
// Transformer la réponse pour qu\'elle corresponde à notre format attendu
const transformedImages = result.results.map(item => ({
id: item.url.split('/').pop().split('.')[0], // Extraire l\'UUID de l\'URL
image: {
original: {
url: item.url
}
},
artist: {
name: item.artist_name,
href: item.artist_href
},
source: item.source_url
}));
return { images: transformedImages };
}
Créer le deuxième service API de secours :
// src/services/api/nekosApi.js
const NEKOS_API_URL = "https://api.nekosapi.com/v3/images/random?limit=21&rating=safe";
export async function fetchNekosImages() {
const response = await fetch(NEKOS_API_URL, {
method: "GET",
});
if (!response.ok) {
throw new Error(`Erreur de l\'API Nekos : ${response.status}`);
}
const result = await response.json();
// Transformer la réponse pour qu\'elle corresponde à notre format attendu
const transformedImages = result.items.map(item => ({
id: item.id,
image: {
original: {
url: item.image_url
}
}
}));
return { images: transformedImages };
}
Construire le mécanisme de secours de l'API :
// src/services/api/imageService.js
import { fetchNekosiaImages } from "./nekosiaApi";
import { fetchNekosImages } from "./nekosApi";
import { fetchNekosBestImages } from "./nekosBestApi";
export async function fetchImages() {
try {
// Essayer d\'abord l\'API principale
return await fetchNekosiaImages();
} catch (error) {
console.warn("L\'API principale a échoué, tentative de secours :", error);
// Essayer la première API de secours
try {
return await fetchNekosBestImages();
} catch (fallbackError) {
console.warn("La première API de secours a échoué, tentative du deuxième secours :", fallbackError);
// Essayer la deuxième API de secours
try {
return await fetchNekosImages();
} catch (secondFallbackError) {
console.error("Toutes les API d\'images ont échoué :", secondFallbackError);
throw new Error("Toutes les API d\'images ont échoué");
}
}
}
}
Utiliser le service d'images :
// src/hooks/useFetch.js
import { useState, useEffect } from "react";
import { fetchImages } from "../services/api/imageService";
export default function useFetch() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const result = await fetchImages();
setData(result);
} catch (err) {
setError(err.message || 'Une erreur est survenue');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
error,
fetchData,
};
}
Fonctionnalités clés de notre implémentation API :
Plusieurs sources API :
API principale (Nekosia) : Fournit des images anime de haute qualité
Premier secours (Nekos Best) : Inclut des informations sur l'artiste
Deuxième secours (Nekos) : Sauvegarde simple et fiable
Format de données cohérent :
- Toutes les API transforment leurs réponses pour qu'elles correspondent à notre format attendu :
{
images: [
{
id: string,
image: {
original: {
url: string
}
}
}
]
}
Gestion robuste des erreurs :
Valide les réponses de l'API
Vérifie les URL d'images valides
Fournit des messages d'erreur détaillés
Mécanisme de secours élégant
Fonctionnalités de sécurité :
Filtre de contenu sûr (
rating=safe)Limitation du nombre d'images (21 images)
Validation des URL
Validation du format de réponse
Considérations de performance :
Tailles d'images optimisées
Balises de contenu filtrées
Transformation de données efficace
Appels API minimaux
Cette implémentation garantit que notre jeu dispose d'une source fiable d'images tout en gérant les échecs potentiels de l'API de manière élégante. Le format de données cohérent sur toutes les API facilite le passage de l'une à l'autre sans affecter la fonctionnalité du jeu.
Tester l'application
Les tests sont une partie cruciale du développement de toute application, et pour notre jeu de mémoire, nous avons mis en place une stratégie de test complète en utilisant des outils et des pratiques modernes. Plongeons dans la manière dont nous avons structuré nos tests et certains modèles de test clés que nous avons utilisés.

Pile de tests
Vitest : Notre framework de test principal, choisi pour sa rapidité et son intégration transparente avec Vite
React Testing Library : Pour tester les composants React avec une approche centrée sur l'utilisateur
@testing-library/user-event : Pour simuler les interactions utilisateur
jsdom : Pour créer un environnement DOM dans nos tests
Modèles de test clés
Les tests ont été une partie cruciale pour garantir la fiabilité et la maintenabilité de ce jeu de mémoire. J'ai mis en place une stratégie de test complète en utilisant React Testing Library et Vitest, en me concentrant sur plusieurs domaines clés :
1. Test des composants
J'ai écrit des tests approfondis pour mes composants React afin de m'assurer qu'ils s'affichent correctement et se comportent comme prévu. Par exemple, le composant CardsGrid, qui est le cœur du jeu, a une couverture de test approfondie incluant :
Les états de rendu initiaux
Les états de chargement
La gestion des erreurs
Le suivi des scores
Le comportement des interactions avec les cartes
2. Mocking des tests
Pour garantir des tests fiables et rapides, j'ai mis en place plusieurs stratégies de mocking :
Les opérations de stockage local utilisant le hook useLocalStorage
Les appels API utilisant le hook
useFetchLes gestionnaires d'événements et les mises à jour d'état
3. Bonnes pratiques de test
Tout au long de la mise en œuvre de mes tests, j'ai suivi plusieurs bonnes pratiques :
Utilisation des hooks
beforeEachetafterEachpour réinitialiser l'état entre les testsTest des interactions utilisateur en utilisant
fireEventde React Testing LibraryÉcriture de tests qui ressemblent à la manière dont les utilisateurs interagissent avec l'application
Test des scénarios de succès et d'erreur
Isolation des tests en utilisant un mocking approprié
4. Outils de test
Le projet utilise des outils et bibliothèques de test modernes :
Vitest : Comme exécuteur de tests
React Testing Library : Pour tester les composants React
@testing-library/jest-dom : Pour des assertions de test DOM améliorées
@testing-library/user-event : Pour simuler les interactions utilisateur
Cette approche de test complète m'a aidé à détecter les bugs tôt, à garantir la qualité du code et à rendre le refactoring plus sûr et plus gérable.
Optimisations
Pour garantir des performances fluides, en particulier sur les appareils mobiles, nous avons mis en place plusieurs techniques d'optimisation :
Transformation de la réponse
Format de données standardisé sur toutes les API
Extraction efficace des identifiants à partir des URL
Métadonnées d'image structurées pour un accès rapide
Optimisation du réseau
Utilisation du mode
no-corslorsque cela est approprié pour gérer efficacement les problèmes CORSGestion des erreurs avec des codes de statut spécifiques pour un meilleur débogage
Structure de réponse cohérente sur toutes les implémentations d'API
Considérations pour les mobiles d'abord
Stratégie de chargement des images optimisée
Gestion efficace des erreurs pour éviter les nouvelles tentatives inutiles
Transformation de données rationalisée pour réduire la surcharge de traitement
Améliorations futures
Il existe plusieurs façons d'améliorer davantage ce projet :
Mise en cache des réponses de l'API
Implémenter la mise en cache du stockage local pour les images fréquemment utilisées
Ajouter une stratégie d'invalidation du cache pour un contenu frais
Implémenter le chargement progressif des images
Optimisations des performances
Ajouter le chargement paresseux des images pour un meilleur temps de chargement initial
Implémenter la mise en file d'attente des requêtes pour une meilleure gestion de la bande passante
Ajouter la compression des réponses pour un transfert de données plus rapide
Améliorations de la fiabilité
Ajouter la vérification de l'état de l'API avant les tentatives
Implémenter des mécanismes de nouvelle tentative avec un délai exponentiel
Ajouter un modèle de disjoncteur pour les API défaillantes
Analytique et surveillance
Suivre les taux de réussite de l'API
Surveiller les temps de réponse
Implémenter le basculement automatique de l'API en fonction des métriques de performance
Cette implémentation robuste garantit que notre jeu reste fonctionnel et performant même dans des conditions réseau défavorables ou en cas d'indisponibilité de l'API, tout en laissant de la place pour des améliorations et optimisations futures.
Conclusion
Construire ce jeu de mémoire a été plus qu'une simple création d'une alternative amusante et sans publicité pour les enfants - c'est un exercice de mise en œuvre des meilleures pratiques de développement web moderne tout en résolvant un problème réel.
Le projet démontre comment combiner une architecture réfléchie, des tests robustes et des mécanismes de secours fiables peut aboutir à une application prête pour la production, à la fois divertissante et éducative.
🛠️ Points clés à retenir
Développement centré sur l'utilisateur
Commencé avec un problème clair (les jeux remplis de publicités affectant l'expérience utilisateur)
Implémenté des fonctionnalités qui améliorent le gameplay sans interruptions
Maintenu l'accent sur la performance et la fiabilité sur tous les appareils
Excellence technique
Utilisé des modèles React modernes et des hooks pour un code propre et maintenable
Implémenté une stratégie de test complète garantissant la fiabilité
Créé un système de secours API robuste pour un gameplay ininterrompu
Performance d'abord
Adopté une approche mobile-first avec un design réactif
Optimisé le chargement et la gestion des images
Implémenté une gestion d'état et des stratégies de mise en cache efficaces
📚 Résultats d'apprentissage
Ce projet montre comment des jeux en apparence simples peuvent être d'excellents véhicules pour la mise en œuvre de solutions techniques complexes. De l'architecture des composants aux secours API, chaque fonctionnalité a été construite avec la scalabilité et la maintenabilité à l'esprit, prouvant que même les projets de loisirs peuvent maintenir une qualité de code de niveau professionnel.
🔮 Aller de l'avant
Bien que le jeu atteigne avec succès son objectif principal de fournir une expérience agréable et sans publicité, les améliorations futures documentées fournissent une feuille de route claire pour l'évolution. Qu'il s'agisse de mettre en œuvre des optimisations supplémentaires ou d'ajouter de nouvelles fonctionnalités, la fondation est solide et prête pour l'expansion.
Le jeu de mémoire est un témoignage de la manière dont les projets personnels peuvent à la fois résoudre des problèmes réels et servir de plateformes pour la mise en œuvre des meilleures pratiques en développement web moderne. N'hésitez pas à explorer le code, à contribuer ou à l'utiliser comme inspiration pour vos propres projets !