Onlangs, terwijl ik naar mijn kind 🧒🏻 keek die gratis geheugenspelletjes speelde op haar tablet, merkte ik dat ze worstelde met een overweldigend aantal advertenties en irritante pop-upbanners.
Dit inspireerde me om een vergelijkbaar spel voor haar te bouwen. Aangezien ze op dit moment dol is op anime, besloot ik het spel te maken met schattige anime-afbeeldingen.
In dit artikel zal ik je begeleiden bij het proces van het bouwen van het spel voor jezelf of je kinderen 🎮.
We zullen beginnen met het verkennen van de spelfuncties, daarna de technologiestapel en projectstructuur behandelen, beide zijn eenvoudig. Tot slot zullen we optimalisaties bespreken en zorgen voor soepel spelen op mobiele apparaten 📱.
Als je het lezen wilt overslaan, klik dan hier 💁 voor de GitHub-opslagplaats 🙌. En hier kun je de live demo zien.
Inhoudsopgave
Projectbeschrijving
In deze tutorial zullen we een uitdagend geheugenspel met React bouwen dat je herinneringsvermogen test. Je doel is om unieke anime-afbeeldingen aan te klikken zonder twee keer op dezelfde te klikken. Elke unieke klik levert je punten op, maar wees voorzichtig – als je twee keer op dezelfde afbeelding klikt, wordt je voortgang gereset.
Spelfuncties:
-
🎯 Dynamische gameplay die je geheugen uitdaagt
-
🔄 Kaarten worden na elke klik geschud om de moeilijkheidsgraad te verhogen
-
🏆 Score bijhouden met behoud van de beste score
-
😺 Schattige anime afbeeldingen van de Nekosia API
-
✨ Soepele laadtijden en animaties
-
📱 Responsief ontwerp voor alle apparaten
-
🎨 Schone, moderne UI
Het spel helpt je je geheugenvaardigheden te testen terwijl je geniet van schattige anime afbeeldingen. Kun je de perfecte score behalen?
Hoe te spelen
-
Klik op elke kaart om te beginnen
-
Vergeet niet welke kaarten je hebt geklikt
-
Probeer alle kaarten precies één keer te klikken
-
Bekijk je score groeien met elke unieke selectie
-
Blijf dan spelen om je beste score te verbeteren
De Tech Stack
Hier is een lijst van de belangrijkste technologieën die we zullen gebruiken:
-
NPM – Een pakketbeheerder voor JavaScript die helpt bij het beheren van afhankelijkheden en scripts voor het project.
-
Vite – Een buildtool die een snelle ontwikkelomgeving biedt, specifiek geoptimaliseerd voor moderne webprojecten.
-
React – Een populaire JavaScript-bibliotheek voor het bouwen van gebruikersinterfaces, waardoor efficiënte weergave en toestandbeheer mogelijk zijn.
-
CSS-modules – Een oplossing voor opmaak die CSS beperkt tot individuele componenten, waardoor opmaakconflicten worden voorkomen en onderhoudbaarheid wordt gegarandeerd.
Laten we het spel bouwen
Vanaf dit punt zal ik je begeleiden bij het proces dat ik heb gevolgd bij het bouwen van dit spel.
Projectstructuur en architectuur
Bij het bouwen van dit memory cards spel heb ik de codebase zorgvuldig georganiseerd om onderhoudbaarheid, schaalbaarheid en een duidelijke scheiding van verantwoordelijkheden te waarborgen. Laten we de structuur verkennen en de redenering achter elke beslissing:
Architectuur op basis van componenten
Ik koos voor een architectuur op basis van componenten om verschillende redenen:
-
Modulariteit: Elk onderdeel is zelfstandig met zijn eigen logica en stijlen
-
Herbruikbaarheid: Componenten zoals
Card
enLoader
kunnen opnieuw worden gebruikt in de applicatie -
Onderhoudbaarheid: Gemakkelijker te debuggen en individuele componenten aan te passen
-
Testen: Componenten kunnen geïsoleerd worden getest
Component Organisatie
- Card Component
-
Opgesplitst in zijn eigen map omdat het een kernspel element is
-
Bevat zowel JSX- als SCSS-modules voor encapsulatie
-
Handelt individuele kaartweergave, laadstatussen en klikgebeurtenissen af
- CardsGrid Component
-
Beheert de lay-out van het speelbord
-
Handelt kaartenschudden en distributie af
-
Beheert het responsieve gridlayout voor verschillende schermformaten
- Loader Component
-
Herbruikbare laadindicator
-
Verbetering van de gebruikerservaring tijdens het laden van afbeeldingen
-
Kan worden gebruikt door elk component dat laadstatussen nodig heeft
- Header/Footer/Subtitel Componenten
-
Structurele componenten voor app-indeling
-
Koptekst toont de naam van het spel en scores
-
Voettekst toont auteursrecht en versie-informatie
-
Subtitel geeft spelinstructies
CSS-modulesbenadering
Ik gebruikte CSS-modules (.module.scss
-bestanden) voor verschillende voordelen:
-
Scoped Styling: Voorkomt stijllekken tussen componenten
-
Naamconflicten: Genereert automatisch unieke klassenamen
-
Onderhoudbaarheid: Stijlen zijn op dezelfde locatie geplaatst als hun componenten
-
SCSS-functies: Maakt gebruik van SCSS-functies terwijl stijlen modulair blijven
Aangepaste hooks
De hooks
-map bevat aangepaste hooks zoals useFetch:
-
Scheiding van zorgen: Isoleert de logica voor gegevens ophalen
-
Herbruikbaarheid: Kan worden gebruikt door elk component dat afbeeldingsgegevens nodig heeft
-
State Management: Beheert laad-, fout- en gegevenstoestanden
-
Prestaties: Implementeert optimalisaties zoals controle over afbeeldingsgrootte
Rootniveau bestanden
App.jsx:
-
Fungeert als toegangspunt van de applicatie
-
Beheert de wereldwijde toestand en routing (indien nodig)
-
Coördineert de samenstelling van componenten
-
Handelt layouts op topniveau af
Prestatieoverwegingen
De structuur ondersteunt prestatieoptimalisaties:
-
Code-splitting: Componenten kunnen indien nodig laag voor laag worden geladen
-
Memoïsatie: Componenten kunnen effectief worden gememoïseerd
-
Stijl Laden: CSS-modules maken efficiënt laden van stijlen mogelijk
-
Beheer van bronnen: Afbeeldingen en bronnen zijn goed georganiseerd
Schaalbaarheid
Deze structuur maakt gemakkelijke schaalbaarheid mogelijk:
-
Nieuwe functies kunnen worden toegevoegd als nieuwe componenten
-
Extra hooks kunnen worden gemaakt voor nieuwe functionaliteit
-
Stijlen blijven onderhoudbaar naarmate de app groeit
-
Testen kan op elk niveau worden geïmplementeerd
Ontwikkelervaring
De structuur verbetert de ontwikkelaarservaring:
-
Duidelijke bestandsorganisatie
-
Intuïtieve locaties van componenten
-
Gemakkelijk specifieke functies vinden en aanpassen
-
Ondersteunt efficiënte samenwerking
Deze architectuur bleek bijzonder waardevol bij het optimaliseren van het spel voor tabletgebruik, omdat het me in staat stelde om:
-
Gemakkelijk prestatieknelpunten identificeren en optimaliseren
-
Tablet-specifieke stijlen toevoegen zonder andere apparaten te beïnvloeden
-
Ladenstoestanden implementeren voor een betere mobiele ervaring
-
Schone scheiding handhaven tussen spellogica en UI-componenten
Goed, laten we nu gaan coderen.
Stapsgewijze bouwgids
1. Projectinstellingen
Opzetten van de ontwikkelomgeving
Om te beginnen met een schoon React-project, opent u uw terminal-app en voert u de volgende commando’s uit (u kunt de naam van uw projectmap naar wens noemen – in mijn geval is de naam ‘memory-card’):
npm create vite@latest memory-card -- --template react
cd memory-card
npm install
Installeer de vereiste afhankelijkheden
De enige afhankelijkheden die we in dit project zullen gebruiken, zijn het hook-pakket van UI.dev (trouwens, hier kunt u een goed uitgelegd artikel vinden over hoe rendering werkt in React).
De andere afhankelijkheid is de bekende CSS-voorverwerker, SASS, die we nodig zullen hebben om onze CSS-modules in SASS te kunnen schrijven in plaats van reguliere CSS.
npm install @uidotdev/usehooks sass
Configureer Vite en projectinstellingen
Bij het instellen van ons project moeten we enkele specifieke configuratieaanpassingen maken om SASS-waarschuwingen te behandelen en onze ontwikkelervaring te verbeteren. Zo kunt u Vitest configureren:
// 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, // Dempt SASS-afhankelijkheidswaarschuwingen
charset: false // Voorkomt charset-waarschuwingen in recente SASS-versies
}
}
}
});
Houd er rekening mee dat de meeste van deze configuraties automatisch voor u worden gegenereerd wanneer u het project aanmaakt met Vite. Hier is wat er gebeurt:
-
SASS-configuratie:
-
quietDeps: true
: Dit dempt de waarschuwingen over verouderde afhankelijkheden in SASS-modules. Bijzonder handig bij het werken met SASS/SCSS-bestanden van derden. -
charset: false
: Voorkomt de “@charset” waarschuwing die verschijnt in nieuwere versies van SASS wanneer speciale tekens worden gebruikt in uw stylesheets.
-
-
Testconfiguratie:
-
globals: true
: Maakt testfuncties wereldwijd beschikbaar in testbestanden -
environment: 'jsdom'
: Biedt een DOM-omgeving voor testen -
setupFiles
: Verwijst naar ons test-installatiebestand
-
Deze configuraties helpen een schonere ontwikkelervaring te creëren door onnodige waarschuwingsberichten in de console te verwijderen, de juiste testomgevingsconfiguraties op te zetten en ervoor te zorgen dat SASS/SCSS-verwerking soepel verloopt.
Je ziet mogelijk waarschuwingen in je console zonder deze configuraties wanneer:
-
Het gebruik van SASS/SCSS-functies of het importeren van SASS-bestanden
-
Tests uitvoeren die DOM-manipulatie vereisen
-
Het gebruik van speciale tekens in je stylesheets
2. Het bouwen van de Componenten
Maak de Kaart Component
Laten we eerst onze basis kaartcomponent maken die individuele afbeeldingen zal weergeven:
// 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;
De Card-component is een fundamenteel bouwblok van ons spel. Het is verantwoordelijk voor het weergeven van individuele afbeeldingen en het afhandelen van spelerinteracties. Laten we de implementatie ervan uiteenzetten:
Props-uitleg:
-
image
: (string)-
De URL van de afbeelding die moet worden weergegeven en die wordt ontvangen van onze API-service.
-
Het wordt rechtstreeks gebruikt in het src-attribuut van het img-element.
-
-
id
: (string)-
Unieke identificatie voor elke kaart die essentieel is voor het bijhouden van welke kaarten zijn aangeklikt.
-
Het wordt doorgegeven aan de
processTurn
callback wanneer er op een kaart wordt geklikt.
-
-
category
: (string)-
Beschrijft het type afbeelding (bijvoorbeeld “anime”, “neko”) en wordt gebruikt in het alt-attribuut voor een betere toegankelijkheid.
Het helpt bij SEO en schermlezers.
-
-
processTurn
: (functie)-
Callback functie doorgegeven vanuit het oudercomponent die de spellogica afhandelt wanneer een kaart wordt geklikt.
-
Het beheert ook score-updates en spelstatuswijzigingen en bepaalt of een kaart al eerder is geklikt.
-
-
isLoading
: (boolean)-
Regelt of een laadstatus moet worden weergegeven. Wanneer waar, wordt in plaats van de afbeelding een Laadindicator component weergegeven.
Het verbetert de gebruikerservaring tijdens het laden van de afbeelding.
-
Component-styling:
// 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%);
}
}
Gebruik in het component:
<Card
key={getKey()}
imgUrl={item?.image?.original?.url || ""}
imageId={item?.id}
categoryName={item?.category}
processTurn={(imageId) => processTurn(imageId)}
/>
Belangrijkste kenmerken:
-
Prestatieoptimalisatie:
-
Gebruikt
React.memo
om onnodige herrenderingen te voorkomen -
Implementeert
useCallback
voor event handlers -
Beheert de laadstatus intern voor een betere UX
-
-
Beheer van de laadstatus:
-
Interne
isLoading
-status houdt het laden van afbeeldingen bij -
Toont een Loader-component met een bericht tijdens het laden
-
Verbergt de afbeelding totdat deze volledig is geladen met behulp van CSS-klassen
-
-
Evenementverwerking:
-
handleImageLoad
: Beheert de overgang van de laadstatus -
handleClick
: Verwerkt spelersbewegingen via deprocessTurn
callback
-
Bouw de CardsGrid Component
Dit is onze hoofdgamecomponent die de spelstatus, scoringslogica en kaartinteracties beheert. Laten we de implementatie ervan uiteenzetten:
// 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) {
// State Management
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);
// Aangepaste hook voor het ophalen van afbeeldingen
const { data: fetchedData, fetchData, error } = useFetch();
// Afbeeldingen bijwerken wanneer er nieuwe gegevens worden opgehaald
useEffect(() => {
if (fetchedData?.images) {
setImages(fetchedData.images);
setIsLoading(false);
// Geklikte afbeeldingen resetten wanneer een nieuwe batch wordt geladen
setClickedImages([]);
}
}, [fetchedData]);
// Hulpfunctie om de beste score bij te werken
function updateBestScore(currentScore) {
if (currentScore > bestScore) {
setBestScore(currentScore);
}
}
// Kernspellogica
function processTurn(imageId) {
const newClickedImages = [...clickedImages, imageId];
setClickedImages(newClickedImages);
// Als er twee keer op dezelfde afbeelding wordt geklikt, alles resetten
if (clickedImages.includes(imageId)) {
// Werk de beste score bij indien nodig
updateBestScore(score);
setClickedImages([]);
setScore(0);
} else {
// Succesvolle kaartselectie afhandelen
const newScore = score + 1;
setScore(newScore);
// Controleren op perfecte score (alle kaarten één keer geklikt)
if (newClickedImages.length === images.length) {
updateBestScore(newScore);
fetchData();
setClickedImages([]);
} else {
// De afbeeldingen door elkaar halen
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);
Componentstyling:
.container {
display: grid;
gap: 1rem 1rem;
grid-template-columns: auto; /* Standaard: één kolom voor mobiel eerst */
background-color: #2196f3;
padding: 0.7rem;
cursor: pointer;
}
@media (min-width: 481px) {
.container {
grid-template-columns: auto auto; /* Twee kolommen voor tablets en hoger */
}
}
@media (min-width: 769px) {
.container {
grid-template-columns: auto auto auto; /* Drie kolommen voor desktops en groter */
}
}
Belangrijkste functies Uitsplitsing:
-
State Management:
-
Gebruikt
useState
voor component-level state -
Implementeert
useLocalStorage
voor persistente spelgegevens:-
clickedImages
: Houdt bij welke kaarten zijn aangeklikt -
score
: Huidige gamescore -
bestScore
: Hoogste behaalde score
-
-
Beheert laadstatus voor het ophalen van afbeeldingen
-
Shuffle de kaarten
-
-
Spellogica:
-
processTurn
: Behandelt speler zetten-
Houdt dubbele klikken bij
-
Werkt scores bij
-
Beheert perfecte score scenario’s
-
-
updateBestScore
: Werkt hoge score bij wanneer nodig -
Haalt automatisch nieuwe afbeeldingen op wanneer een ronde is voltooid
-
-
Gegevens ophalen:
-
Gebruikt aangepaste
useFetch
hook voor afbeeldingsgegevens -
Behandelt laden en foutstatussen
-
Werkt afbeeldingen bij wanneer nieuwe gegevens worden opgehaald
-
-
Prestatieoptimalisatie:
-
Component omhuld in
React.memo
-
Efficiënte statusupdates
-
Responsieve rasterindeling
-
-
Persistentie:
-
Spelstatus blijft behouden bij het herladen van de pagina
-
Beste-score bijhouden
-
Huidige voortgang van het spel opslaan
-
Voorbeeld van gebruik:
...
...
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;
De CardsGrid-component dient als het hart van ons memory card-spel en beheert:
-
Spelstatus en logica
-
Score bijhouden
-
Interacties met kaarten
-
Afbeeldingen laden en weergeven
-
Responsieve lay-out
-
Data persistentie
Deze implementatie biedt een soepele game-ervaring met behoud van code leesbaarheid en onderhoudbaarheid door een duidelijke scheiding van verantwoordelijkheden en adequaat beheer van de status.
3. Implementatie van de API-laag
Ons spel maakt gebruik van een robuuste API-laag met meerdere fallback-opties om betrouwbare afbeeldingslevering te garanderen. Laten we elk service implementeren en het fallback-mechanisme.
Opzetten van de primaire API-service:
// 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 };
}
Creëer de eerste fallback-API-service:
// 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();
// Transformeer de respons om overeen te komen met ons verwachte formaat
const transformedImages = result.results.map(item => ({
id: item.url.split('/').pop().split('.')[0], // Haal UUID uit URL
image: {
original: {
url: item.url
}
},
artist: {
name: item.artist_name,
href: item.artist_href
},
source: item.source_url
}));
return { images: transformedImages };
}
Creëer de tweede fallback-API-service:
// 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();
// Transformeer de respons om overeen te komen met ons verwachte formaat
const transformedImages = result.items.map(item => ({
id: item.id,
image: {
original: {
url: item.image_url
}
}
}));
return { images: transformedImages };
}
Bouw het API-terugvalmechanisme:
// src/services/api/imageService.js
import { fetchNekosiaImages } from "./nekosiaApi";
import { fetchNekosImages } from "./nekosApi";
import { fetchNekosBestImages } from "./nekosBestApi";
export async function fetchImages() {
try {
// Probeer eerst de primaire API
return await fetchNekosiaImages();
} catch (error) {
console.warn("Primary API failed, trying fallback:", error);
// Probeer eerst de terugval-API
try {
return await fetchNekosBestImages();
} catch (fallbackError) {
console.warn("First fallback API failed, trying second fallback:", fallbackError);
// Probeer vervolgens de tweede terugval-API
try {
return await fetchNekosImages();
} catch (secondFallbackError) {
console.error("All image APIs failed:", secondFallbackError);
throw new Error("All image APIs failed");
}
}
}
}
Gebruik de Image Service:
// 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,
};
}
Belangrijkste kenmerken van onze API-implementatie:
-
Meerdere API-bronnen:
-
Primaire API (Nekosia): Biedt hoogwaardige anime-afbeeldingen
-
Eerste terugval (Nekos Best): Bevat informatie over de artiest
-
Tweede terugval (Nekos): Eenvoudige en betrouwbare back-up
-
-
Consistente gegevensindeling:
- Alle API’s transformeren hun antwoorden om overeen te komen met ons verwachte formaat:
{
images: [
{
id: string,
image: {
original: {
url: string
}
}
}
]
}
-
Robuuste Foutafhandeling:
-
Controleert API-responsen
-
Controleert geldige afbeeldings-URL’s
-
Biedt gedetailleerde foutmeldingen
-
Soepele fallback-mechanisme
-
-
Veiligheidsfuncties:
-
Veilige inhoudsfiltering (
rating=veilig
) -
Afbeeldingslimiet (21 afbeeldingen)
-
URL-validatie
-
Validatie van responsindeling
-
-
Prestatieoverwegingen:
-
Geoptimaliseerde afbeeldingsgroottes
-
Gefilterde inhoudstags
-
Efficiënte gegevenstransformatie
-
Minimale API-oproepen
-
Deze implementatie zorgt ervoor dat ons spel een betrouwbare bron van afbeeldingen heeft, terwijl het potentieel API-fouten netjes afhandelt. Het consistente gegevensformaat over alle API’s maakt het gemakkelijk om tussen hen te schakelen zonder de functionaliteit van het spel te beïnvloeden.
De App testen
Testen is een cruciaal onderdeel van elke applicatieontwikkeling, en voor ons Memory Card Game hebben we een uitgebreide teststrategie geïmplementeerd met behulp van moderne tools en praktijken. Laten we eens kijken naar hoe we onze tests hebben gestructureerd en enkele belangrijke testpatronen die we hebben gebruikt.
Test Stack
-
Vitest: Ons primaire testframework, gekozen vanwege zijn snelheid en naadloze integratie met Vite
-
React Testing Library: Voor het testen van React-componenten met een op de gebruiker gerichte benadering
-
@testing-library/user-event: Voor het simuleren van gebruikersinteracties
-
jsdom: Voor het creëren van een DOM-omgeving in onze tests
Kern testpatronen
Tests waren een cruciaal onderdeel om de betrouwbaarheid en onderhoudbaarheid van dit Memory Card-spel te waarborgen. Ik heb een uitgebreide teststrategie geïmplementeerd met behulp van React Testing Library en Vitest, met de focus op verschillende belangrijke gebieden:
1. Component Testing
Ik heb uitgebreide tests geschreven voor mijn React-componenten om ervoor te zorgen dat ze correct renderen en zich gedragen zoals verwacht. Zo heeft bijvoorbeeld de CardsGrid
-component, die het hart van het spel vormt, een grondige testdekking, inclusief:
-
Initiële renderingsstatussen
-
Laadstatussen
-
Foutafhandeling
-
Score bijhouden
-
Gedrag bij kaartinteractie
2. Test Mocking
Om betrouwbare en snelle tests te garanderen, heb ik verschillende mockstrategieën geïmplementeerd:
-
Bewerkingen met lokale opslag met behulp van de useLocalStorage-hook
-
API-aanroepen met behulp van de
useFetch
-hook -
Gebeurtenishandlers en statusupdates
3. Best Practices voor Testen
Tijdens mijn testimplementatie heb ik verschillende best practices gevolgd:
-
Het gebruik van
beforeEach
enafterEach
hooks om de status tussen tests te resetten -
Het testen van gebruikersinteracties met behulp van
fireEvent
van React Testing Library -
Het schrijven van tests die lijken op hoe gebruikers met de app omgaan
-
Het testen van zowel succes- als foutscenario’s
-
Het isoleren van tests met behulp van correcte mocking
4. Testtools
Het project maakt gebruik van moderne testtools en bibliotheken:
-
Vitest: Als testrunner
-
React Testing Library: Voor het testen van React-componenten
-
@testing-library/jest-dom: Voor verbeterde DOM-testasserties
-
@testing-library/user-event: Voor het simuleren van gebruikersinteracties
Deze uitgebreide testaanpak heeft me geholpen om bugs vroegtijdig op te sporen, de codekwaliteit te waarborgen en het refactoren veiliger en beheersbaarder te maken.
Optimalisaties
Om een soepele prestatie te garanderen, vooral op mobiele apparaten, hebben we verschillende optimalisatietechnieken geïmplementeerd:
-
Respons Transformatie
-
Gestandaardiseerd gegevensformaat voor alle API’s
-
Efficiënte ID-extractie uit URL’s
-
Gestructureerde afbeeldingsmetadata voor snelle toegang
-
-
Netwerkoptimalisatie
-
Het gebruik van de
no-cors
modus waar nodig om CORS-problemen efficiënt op te lossen -
Foutafhandeling met specifieke statuscodes voor beter debuggen
-
Consistente responsstructuur voor alle API-implementaties
-
-
Mobiel-Eerst Overwegingen
-
Geoptimaliseerde strategie voor het laden van afbeeldingen
-
Efficiënte foutafhandeling om onnodige herhalingen te voorkomen
-
Gestroomlijnde datatransformatie om verwerkingskosten te verlagen
-
Toekomstige Verbeteringen
Er zijn een paar manieren waarop we dit project verder kunnen verbeteren:
-
API Respons Caching
-
Implementeer lokale opslag caching voor vaak gebruikte afbeeldingen
-
Voeg cache-invalideringsstrategie toe voor verse inhoud
-
Implementeer progressief laden van afbeeldingen
-
-
Prestatie-optimalisaties
-
Voeg lazy loading van afbeeldingen toe voor een betere initiële laadtijd
-
Implementeer verzoekwachtrijen voor een beter beheer van de bandbreedte
-
Voeg responscompressie toe voor snellere gegevensoverdracht
-
-
Betrouwbaarheidsverbeteringen
-
Voeg API-gezondheidscontrole toe voordat pogingen worden ondernomen
-
Implementeer herhalingsmechanismen met exponentiële vertraging
-
Voeg circuit breaker-patroon toe voor niet-werkende API’s
-
-
Analytics en Monitoring
-
Volg succespercentages van API’s
-
Monitor responstijden
-
Implementeer automatische API-overschakeling op basis van prestatie-indicatoren
-
Deze robuuste implementatie zorgt ervoor dat ons spel functioneel en prestatiegericht blijft, zelfs onder ongunstige netwerkomstandigheden of bij niet-beschikbare API’s, terwijl er nog steeds ruimte is voor toekomstige verbeteringen en optimalisaties.
Conclusie
Het bouwen van dit Memory Card Game is meer dan alleen het creëren van een leuk, reclamevrij alternatief voor kinderen – het is een oefening geweest in het implementeren van moderne webontwikkelingsbest practices bij het oplossen van een echt probleem.
Het project toont aan hoe het combineren van doordachte architectuur, robuuste testen en betrouwbare fallback-mechanismen kan resulteren in een productieklare applicatie die zowel vermakelijk als educatief is.
🗝️ Belangrijke punten
-
Gebruikersgerichte ontwikkeling
-
Begonnen met een duidelijk probleem (advertentievullende spellen die de gebruikerservaring beïnvloeden)
-
Functies geïmplementeerd die de gameplay verbeteren zonder onderbrekingen
-
Focust gehouden op prestaties en betrouwbaarheid op verschillende apparaten
-
-
Technische excellentie
-
Moderne React-patronen en hooks benut voor schone, onderhoudbare code
-
Uitgebreide teststrategie geïmplementeerd die betrouwbaarheid waarborgt
-
Een robuust API-fallbacksysteem gecreëerd voor ononderbroken gameplay
-
-
Eerst Prestaties
-
Heeft een mobile-first benadering aangenomen met responsief ontwerp
-
Geoptimaliseerde afbeeldingslading en -verwerking
-
Geïmplementeerde efficiënte toestandsbeheer- en cachingstrategieën
-
📚 Leerresultaten
Deze project toont aan hoe ogenschijnlijk eenvoudige spellen uitstekende voertuigen kunnen zijn voor het implementeren van complexe technische oplossingen. Van componentarchitectuur tot API-noodplannen, elk kenmerk is gebouwd met schaalbaarheid en onderhoudbaarheid in gedachten, waarbij wordt aangetoond dat zelfs hobbyprojecten professionele codekwaliteit kunnen behouden.
🔮 Vooruitkijkend
Terwijl het spel succesvol zijn primaire doel bereikt om een advertentievrije, plezierige ervaring te bieden, bieden de gedocumenteerde toekomstige verbeteringen een duidelijke routekaart voor evolutie. Of het nu gaat om het implementeren van aanvullende optimalisaties of het toevoegen van nieuwe functies, het fundament is solide en klaar voor uitbreiding.
Het Memory Card Game staat als een testament voor hoe persoonlijke projecten zowel echte problemen kunnen oplossen als dienen als platform voor het implementeren van best practices in moderne webontwikkeling. Voel je vrij om de code te verkennen, bij te dragen, of het te gebruiken als inspiratie voor je eigen projecten!
Source:
https://www.freecodecamp.org/news/how-to-build-a-memory-card-game-using-react/