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 agaçantes.
Cela m’a inspiré à créer un jeu similaire pour elle. Comme elle est actuellement fan d’anime, j’ai décidé de créer le jeu en utilisant des images mignonnes au style anime.
Dans cet article, je vais vous guider à travers le processus de construction du jeu pour vous-même ou vos enfants 🎮.
Nous commencerons par explorer les fonctionnalités du jeu, puis nous aborderons la pile technologique et la structure du projet, qui sont toutes deux simples. Enfin, nous discuterons des optimisations et de l’assurance d’un gameplay fluide sur les appareils mobiles 📱.
Si vous souhaitez passer la lecture, ici 💁 se trouve 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 cartes 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:
-
🎯 Gameplay dynamique qui met au défi votre mémoire
-
🔄 Les cartes sont mélangées après chaque clic pour augmenter la difficulté
-
🏆 Suivi du score avec persistance du meilleur score
-
😺 Images animées adorables de l’API Nekosia
-
✨ Transitions et animations de chargement fluides
-
📱 Design réactif pour tous les appareils
-
🎨 Interface utilisateur propre et moderne
Le jeu vous aidera à tester vos compétences en mémoire tout en profitant de jolies images animées. Pouvez-vous atteindre le score parfait?
Comment jouer
-
Cliquez sur n’importe quelle carte pour commencer
-
Souvenez-vous des cartes que vous avez cliquées
-
Essayez de cliquer sur toutes les cartes exactement une fois
-
Regardez votre score augmenter avec chaque sélection unique
-
Ensuite, 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 pour le projet.
-
Vite – Un outil de construction qui offre 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 stylisation qui limite le CSS à des composants individuels, évitant les conflits de styles et garantissant la maintenabilité.
Construisons le Jeu
À partir de ce moment, 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 garantir la maintenabilité, l’évolutivité et une séparation claire des préoccupations. Explorons la structure et le raisonnement derrière chaque décision :
Architecture Basée sur des Composants
J’ai choisi une architecture basée sur des composants pour plusieurs raisons :
-
Modularité: Chaque composant est autonome avec sa propre logique et ses styles
-
Réutilisabilité: Des composants comme
Card
etLoader
peuvent être réutilisés dans toute l’application -
Maintenabilité: Plus facile à déboguer et à modifier individuellement les composants
-
Test: Les composants peuvent être testés de manière isolée
Organisation des composants
- Composant Card
-
Séparé dans son propre répertoire car il s’agit d’un élément central du jeu
-
Contient à la fois des modules JSX et SCSS pour l’encapsulation
-
Gère le rendu individuel des cartes, 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 mise en page 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 tout composant nécessitant des états de chargement
- Composants En-tête/Pied de page/Sous-titre
-
Composants structurels pour la mise en page de l’application
-
L’en-tête affiche le titre du jeu et les scores
-
Le pied de page affiche les informations sur les droits d’auteur et la version
-
Le sous-titre fournit les instructions du jeu
Approche des modules CSS
J’ai utilisé des modules CSS (.module.scss
fichiers) pour plusieurs avantages :
-
Style Scoped: 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 situés au même emplacement que leurs composants
-
Fonctionnalités SCSS: Tire parti des fonctionnalités SCSS tout en maintenant 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 nécessitant des données d’image
-
Gestion de l’é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 :
-
Sert de 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 mises en page de niveau supérieur
Considérations de performance
La structure prend en charge les optimisations de performance:
-
Scission de code: Les composants peuvent être chargés de manière paresseuse si nécessaire
-
Mémoïsation: Les composants peuvent être mémoïsés efficacement
-
Chargement de style: Les modules CSS permettent un chargement de style efficace
-
Gestion des ressources: Les images et 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 évolue
-
Les tests peuvent être mis en œuvre à n’importe quel niveau
Expérience de développement
La structure améliore l’expérience des développeurs :
-
Organisation claire des fichiers
-
Emplacements de composants intuitifs
-
Facilité à trouver et modifier des fonctionnalités spécifiques
-
Prise en charge d’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 des performances
-
Ajouter des styles spécifiques à la tablette sans affecter les autres appareils
-
Mettre en place des états de chargement pour une meilleure expérience mobile
-
Maintenir une séparation claire entre la logique de jeu et les composants d’interface utilisateur
D’accord, passons maintenant à la programmation.
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 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 paquet hook 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 classique.
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, // Silence les avertissements de dépendance SASS
charset: false // Empêche les avertissements 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 lors de travailler avec des fichiers SASS/SCSS de tiers. -
charset: false
: Empêche l’avertissement « @charset » qui apparaît dans les versions plus récentes de SASS lors de l’utilisation de caractères spéciaux dans vos feuilles de style.
-
-
Configuration de test:
-
globals: true
: Rend les fonctions de test globalement disponibles dans les fichiers de test -
environment: 'jsdom'
: Fournit un environnement DOM pour les tests -
setupFiles
: 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 correctement l’environnement de test et en veillant à ce que le traitement SASS/SCSS fonctionne correctement.
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="Loading..." />
</div>
)}
<img
src={imgUrl}
alt={categoryName}
onLoad={handleImageLoad}
className={`${styles.image} ${isLoading ? styles.hidden : ''}`}
/>
</div>
);
});
export default Card;
Le composant Card est un bloc de construction fondamental de notre jeu. Il est responsable de l’affichage des images individuelles et de la gestion des interactions des joueurs. Analysons son implémentation:
Répartition des propriétés :
-
image
: (chaîne)-
L’URL de l’image à afficher reçue de notre service API.
-
Elle est utilisée directement dans l’attribut src de la balise img.
-
-
id
: (chaîne)-
Identifiant unique pour chaque carte, essentiel pour suivre quelles cartes ont été cliquées.
-
Il est transmis à la fonction de rappel
processTurn
lorsqu’une carte est cliquée.
-
-
category
: (chaîne)-
Décrit le type d’image (par exemple, « anime », « neko »), et elle est utilisée dans l’attribut alt pour une meilleure accessibilité.
-
Elle aide au référencement et aux lecteurs d’écran.
-
-
processTurn
: (fonction)-
Fonction de rappel passée du composant parent qui gère la logique du jeu lorsqu’une carte est cliquée.
-
Elle gère également les mises à jour de score et les changements d’état du jeu et détermine si une carte a déjà été cliquée.
-
-
isLoading
: (booléen)-
Contrôle s’il faut afficher un état de chargement. Lorsque c’est vrai, il affiche un composant Loader au lieu de l’image.
-
Cela améliore l’expérience utilisateur pendant le chargement de l’image.
-
Styles 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)}
/>
Caractéristiques clés :
-
Optimisation des performances :
-
Utilise
React.memo
pour éviter des re-rendus inutiles -
Implémente
useCallback
pour les gestionnaires d’événements -
Gère l’état de chargement en interne pour une meilleure expérience utilisateur
-
-
Gestion de l’état de chargement :
-
L’état interne
isLoading
suit le chargement de l’image -
Affiche un composant Loader avec un message pendant le chargement
-
Masque l’image jusqu’à ce qu’elle soit entièrement chargée à l’aide de classes CSS
-
-
Gestion des événements:
-
handleImageLoad
: Gère la transition de l’état de chargement -
handleClick
: Traite les mouvements du joueur via le rappelprocessTurn
-
Construire le composant CardsGrid
Il s’agit de notre composant principal de jeu qui gère l’état du jeu, la logique de score et les interactions avec les cartes. Décortiquons 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 d'aide 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 vous cliquez deux fois sur la même image, réinitialisez tout
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 d'une carte
const newScore = score + 1;
setScore(newScore);
// Vérifier un 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>Failed to fetch data</p>;
}
if (isLoading) {
return <Loader message="Loading new 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);
Styles de composants:
.container {
display: grid;
gap: 1rem 1rem;
grid-template-columns: auto; /* Par défaut: une colonne pour les mobiles en premier */
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 */
}
}
Présentation des principales fonctionnalités:
-
Gestion de l’état:
-
Utilise
useState
pour l’état au niveau du composant -
Implémente
useLocalStorage
pour les données de jeu persistantes :-
clickedImages
: Suit quelles cartes ont été cliquées -
score
: Score actuel du jeu -
bestScore
: Meilleur score réalisé
-
-
Gère l’état de chargement pour le chargement des images
-
Mélange les cartes
-
-
Logique de jeu:
-
processTurn
: Gère les mouvements des joueurs-
Suit les clics en double
-
Met à jour les scores
-
Gère les scénarios de score parfait
-
-
updateBestScore
: Met à jour le meilleur score si nécessaire -
Récupère automatiquement de nouvelles images à la fin d’une manche
-
-
Récupération de données:
-
Utilise un hook personnalisé
useFetch
pour les données d’image -
Gè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.memo
-
Mises à jour d’état efficaces
-
Disposition de grille réactive
-
-
Persistance:
-
L’état du jeu persiste lors des rechargements de page
-
Enregistrement de la progression actuelle du jeu
Suivi du meilleur score
-
Exemple d’utilisation :
...
...
function App() {
const { data, loading, error } = useFetch();
if (loading) return <Loader />;
if (error) return <p>Error: {error}</p>;
return (
<div className={styles.container}>
<Header />
<Subtitle />
<CardsGrid data={data} />
<Footer />
</div>
);
}
export default App;
Le composant CardsGrid sert de cœur de notre jeu de cartes mémoire, gérant :
-
État et logique du jeu
-
Suivi des scores
-
Interactions des cartes
-
Chargement et affichage des images
-
Mise en page réactive
-
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 repli pour garantir une livraison d’images fiable. Implémentons chaque service et le mécanisme de repli.
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(`Nekosia API error: ${response.status}`);
}
const result = await response.json();
if (!result.images || !Array.isArray(result.images)) {
throw new Error('Invalid response format from Nekosia API');
}
const validImages = result.images.filter(item => item?.image?.original?.url);
if (validImages.length === 0) {
throw new Error('No valid images received from Nekosia API');
}
return { ...result, images: validImages };
}
Créer le premier service API de repli :
// 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(`Nekos Best API error: ${response.status}`);
}
const result = await response.json();
// Transformer la réponse pour correspondre à 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 repli :
// 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(`Nekos API error: ${response.status}`);
}
const result = await response.json();
// Transformer la réponse pour correspondre à 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 Repli 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("Primary API failed, trying fallback:", error);
// Essayer la première API de repli
try {
return await fetchNekosBestImages();
} catch (fallbackError) {
console.warn("First fallback API failed, trying second fallback:", fallbackError);
// Essayer la deuxième API de repli
try {
return await fetchNekosImages();
} catch (secondFallbackError) {
console.error("All image APIs failed:", secondFallbackError);
throw new Error("All image APIs failed");
}
}
}
}
Utiliser le Service d’Image :
// 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 || 'An error occurred');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {
data,
loading,
error,
fetchData,
};
}
Caractéristiques Clés de Notre Mise en Œuvre de l’API :
-
Multiple Sources d’API :
-
API Principale (Nekosia) : Fournit des images d’anime de haute qualité
-
Premier Repli (Nekos Best) : Inclut des informations sur les artistes
-
Deuxième Repli (Nekos) : Sauvegarde simple et fiable
-
-
Format de Données Cohérent :
- Toutes les APIs transforment leurs réponses pour correspondre à notre format attendu :
{
images: [
{
id: string,
image: {
original: {
url: string
}
}
}
]
}
-
Gestion des erreurs robuste:
-
Valide les réponses de l’API
-
Vérifie les URL d’image valides
-
Fournit des messages d’erreur détaillés
-
Mécanisme de repli gracieux
-
-
Fonctionnalités de sécurité:
-
Filtrage de contenu sécurisé (
rating=safe
) -
Limitation du nombre d’images (21 images)
-
Validation d’URL
-
Validation du format de réponse
-
-
Considérations de performance:
-
Tailles d’image optimisées
-
Tags de contenu filtrés
-
Transformation efficace des données
-
Appels d’API minimaux
-
Cette implémentation assure que notre jeu dispose d’une source fiable d’images tout en gérant gracieusement les échecs éventuels de l’API. 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
Le test est une partie cruciale de tout développement d’application, et pour notre Memory Card Game, nous avons mis en place une stratégie de test complète en utilisant des outils et des pratiques modernes. Découvrons comment nous avons structuré nos tests et quelques schémas de test clés que nous avons utilisés.
Pile de test
-
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 étaient une partie cruciale pour garantir la fiabilité et la maintenabilité de ce Memory Card Game. J’ai mis en place une stratégie de test complète en utilisant React Testing Library et Vitest, en mettant l’accent sur plusieurs domaines clés:
1. Test de composants
J’ai écrit des tests approfondis pour mes composants React afin de garantir qu’ils se rendent correctement et se comportent comme prévu. Par exemple, le composant CardsGrid
, qui est le cœur du jeu, est largement testé, y compris:
-
États de rendu initiaux
-
États de chargement
-
Gestion des erreurs
-
Suivi du score
-
Comportement d’interaction des cartes
2. Mocking des tests
Pour garantir des tests fiables et rapides, j’ai mis en place plusieurs stratégies de simulation:
-
Opérations de stockage local en utilisant le crochet useLocalStorage
-
Appels API en utilisant le crochet
useFetch
-
Gestionnaires d’événements et mises à jour d’état
3. Meilleures pratiques de test
Tout au long de ma mise en œuvre des tests, j’ai suivi plusieurs meilleures pratiques :
-
Utilisation des hooks
beforeEach
etafterEach
pour réinitialiser l’état entre les tests -
Tester les interactions utilisateur en utilisant
fireEvent
de React Testing Library -
Écrire des tests qui ressemblent à la façon dont les utilisateurs interagissent avec l’application
-
Tester à la fois les scénarios de succès et d’erreur
-
Isoler les tests en utilisant un bon mocking
4. Outils de test
Le projet utilise des outils et bibliothèques de test modernes :
-
Vitest: En tant qu’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 des interactions utilisateur
Cette approche complète du test 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, notamment sur les appareils mobiles, nous avons mis en place plusieurs techniques d’optimisation :
-
Transformation des réponses
-
Format de données standardisé pour toutes les APIs
-
Extraction efficace des identifiants des URL
-
Métadonnées d’image structurées pour un accès rapide
-
-
Optimisation du réseau
-
Utilisation du mode
no-cors
lorsque approprié pour gérer efficacement les problèmes CORS -
Gestion des erreurs avec des codes d’état spécifiques pour un meilleur débogage
-
Structure de réponse cohérente pour toutes les implémentations d’API
-
-
Considérations Mobile-First
-
Stratégie d’optimisation du chargement des images
-
Gestion efficace des erreurs pour éviter les tentatives inutiles
-
Transformation de données rationalisée pour réduire la charge de traitement
-
Améliorations futures
Il existe quelques façons dont nous pourrions améliorer davantage ce projet:
-
Mise en cache des réponses d’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 du 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 une vérification de la santé de l’API avant les tentatives
-
Implémenter des mécanismes de réessayage avec une atténuation exponentielle
-
Ajouter un motif de disjoncteur pour les APIs défaillantes
-
-
Analyse et Surveillance
-
Suivez les taux de succès des API
-
Surveillez les temps de réponse
-
Implémentez un changement automatique d’API basé sur les indicateurs de performance
-
Cette mise en œuvre 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 place à des améliorations et optimisations futures.
Conclusion
Construire ce jeu de cartes mémoire a été plus qu’une simple création d’une alternative amusante et sans publicité pour les enfants : cela a été un exercice d’implémentation des meilleures pratiques modernes de développement web tout en résolvant un problème du monde réel.
Le projet démontre comment la combinaison d’une architecture réfléchie, de tests robustes et de mécanismes de secours fiables peut aboutir à une application prête pour la production, à la fois divertissante et éducative.
🗝️ Principaux points à retenir
-
Développement axé sur l’utilisateur
-
A commencé par un problème clair (jeux remplis de publicités affectant l’expérience utilisateur)
-
Mis en place des fonctionnalités qui améliorent le gameplay sans interruptions
-
Concentré sur la performance et la fiabilité sur tous les appareils
-
-
Excellence technique
-
Exploité les modèles et les hooks React modernes pour un code propre et maintenable
-
Mis en place 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
-
Mis en œuvre des stratégies de gestion d’état et de mise en cache efficaces
-
📚 Résultats d’apprentissage
Ce projet montre comment des jeux apparemment simples peuvent être d’excellents véhicules pour mettre en œuvre des solutions techniques complexes. De l’architecture des composants aux secours d’API, chaque fonctionnalité a été construite en tenant compte de la scalabilité et de la maintenabilité, prouvant que même les projets de loisir peuvent maintenir une qualité de code de niveau professionnel.
🔮 Aller de l’avant
Bien que le jeu réussisse à atteindre son objectif principal de fournir une expérience agréable sans publicités, les améliorations futures documentées offrent une feuille de route claire pour l’évolution. Que ce soit pour mettre en œuvre des optimisations supplémentaires ou ajouter de nouvelles fonctionnalités, la base est solide et prête pour l’expansion.
Le jeu de cartes mémoire témoigne de la manière dont des projets personnels peuvent à la fois résoudre des problèmes concrets et servir de plateformes pour mettre en œuvre les meilleures pratiques en développement web moderne. N’hésitez pas à explorer le code, à contribuer ou à l’utiliser comme source d’inspiration pour vos propres projets !
Source:
https://www.freecodecamp.org/news/how-to-build-a-memory-card-game-using-react/