Недавно, когда я наблюдала за тем, как моя дочка 🧒🏻 играет в бесплатные игры на память на своем планшете, я заметила, что она борется с огромным количеством рекламы и надоедливых всплывающих баннеров.
Это вдохновило меня создать для нее похожую игру. Поскольку сейчас она увлечена аниме, я решила создать игру, используя милые аниме-стилизованные изображения.
В этой статье я расскажу вам о процессе создания игры для вас или ваших детей 🎮.
Мы начнем с изучения особенностей игры, затем рассмотрим стек технологий и структуру проекта, которые оба довольно просты. Наконец, мы обсудим оптимизацию и обеспечение плавной игры на мобильных устройствах 📱.
Если вы хотите пропустить чтение, вот 💁 GitHub-репозиторий 🙌. И вот вы можете посмотреть демонстрацию вживую.
Содержание
Описание проекта
В этом учебнике мы создадим сложную игру на запоминание с карточками на React, которая проверит ваши способности к запоминанию. Ваша цель – нажимать на уникальные аниме-изображения, не нажимая дважды на одно и то же. За каждый уникальный клик вы получаете очки, но будьте осторожны – повторное нажатие на изображение сбрасывает ваш прогресс.
Особенности игры:
-
🎯 Динамичный игровой процесс, проверяющий вашу память
-
🔄 Карты перемешиваются после каждого клика, чтобы увеличить сложность
-
🏆 Отслеживание счета с сохранением лучшего результата
-
😺 Очаровательные аниме изображения из API Nekosia
-
✨ Плавные переходы и анимации загрузки
-
📱 Адаптивный дизайн для всех устройств
-
🎨 Чистый, современный интерфейс
Игра поможет вам протестировать ваши навыки памяти, наслаждаясь милыми аниме картинками. Сможете ли вы достичь идеального результата?
Как играть
-
Нажмите на любую карту, чтобы начать
-
Запомните, какие карты вы нажимали
-
Старайтесь нажимать на все карты ровно один раз
-
Смотрите, как ваш счет растет с каждым уникальным выбором
-
Затем продолжайте играть, чтобы попытаться побить свой лучший результат
Технологический стек
Вот список основных технологий, которые мы будем использовать:
-
NPM – Менеджер пакетов для JavaScript, который помогает управлять зависимостями и скриптами для проекта.
-
Vite – Инструмент сборки, который предоставляет быструю среду разработки, особенно оптимизированную для современных веб-проектов.
-
React – Популярная библиотека JavaScript для создания пользовательских интерфейсов, позволяющая эффективно управлять рендерингом и состоянием.
-
CSS Modules – Решение для стилизации, которое ограничивает области действия CSS отдельными компонентами, предотвращая конфликты стилей и обеспечивая поддерживаемость.
Давайте создадим игру
С этого момента я проведу вас через процесс, который я следовал при создании этой игры.
Структура и архитектура проекта
При создании этой игры на память я тщательно организовал кодовую базу, чтобы обеспечить поддерживаемость, масштабируемость и четкое разделение обязанностей. Давайте рассмотрим структуру и причины каждого решения:
Архитектура на основе компонентов
Я выбрал архитектуру на основе компонентов по нескольким причинам:
-
Модульность: Каждый компонент является автономным с собственной логикой и стилями
-
Повторное использование: Компоненты, такие как
Card
иLoader
, могут быть повторно использованы в приложении -
Поддерживаемость: Проще отлаживать и изменять отдельные компоненты
-
Тестирование: Компоненты могут тестироваться изолированно
Организация компонентов
- Компонент Card
-
Отдельно в своем каталоге, так как это ключевой элемент игры
-
Содержит как модули JSX, так и SCSS для инкапсуляции
-
Обрабатывает рендеринг отдельных карт, состояния загрузки и события кликов
- Компонент CardsGrid
-
Управляет макетом игрового поля
-
Обрабатывает перетасовку и распределение карт
-
Управляет адаптивной сеточной разметкой для различных размеров экрана
- Компонент загрузчика
-
Переиспользуемый индикатор загрузки
-
Улучшает пользовательский опыт во время загрузки изображений
-
Может использоваться любым компонентом, которому нужны состояния загрузки
- Компоненты заголовка/подвала/подзаголовка
-
Структурные компоненты для разметки приложения
-
Заголовок отображает название игры и счета
-
Подвал показывает информацию о праве собственности и версии
-
Подзаголовок предоставляет инструкции по игре
Подход CSS-модулей
Я использовал CSS модули (.module.scss
файлы) по нескольким причинам:
-
Область стилей: Предотвращает утечку стилей между компонентами
-
Конфликты имен: Автоматически генерирует уникальные имена классов
-
Поддерживаемость: Стили размещены рядом с их компонентами
-
Особенности SCSS: Использует возможности SCSS, сохраняя стили модульными
Пользовательские хуки
Директория hooks
содержит пользовательские хуки, такие как useFetch:
-
Разделение обязанностей: Изолирует логику получения данных
-
Переиспользуемость: Может использоваться любым компонентом, нуждающимся в данных изображения
-
Управление состоянием: Обрабатывает состояния загрузки, ошибок и данных
-
Производительность: Реализует оптимизации, такие как контроль размера изображения
Файлы корневого уровня
App.jsx:
-
Служит точкой входа в приложение
-
Управляет глобальным состоянием и маршрутизацией (если необходимо)
-
Координирует компоновку компонентов
-
Обрабатывает макеты верхнего уровня
Рассмотрения производительности
Структура поддерживает оптимизацию производительности:
-
Динамическая загрузка кода: Компоненты могут быть лениво загружены при необходимости
-
Мемоизация: Компоненты могут быть эффективно мемоизированы
-
Загрузка стилей: CSS-модули обеспечивают эффективную загрузку стилей
-
Управление ресурсами: Изображения и ресурсы правильно организованы
Масштабируемость
Эта структура обеспечивает легкое масштабирование:
-
Новые функции могут быть добавлены в виде новых компонентов
-
Могут быть созданы дополнительные хуки для нового функционала
-
Стили остаются управляемыми по мере роста приложения
-
Тестирование может быть реализовано на любом уровне
Опыт разработки
Структура улучшает опыт разработчика:
-
Четкая организация файлов
-
Интуитивно понятные расположения компонентов
-
Легко находить и изменять конкретные функции
-
Поддерживает эффективное сотрудничество
Эта архитектура оказалась особенно ценной при оптимизации игры для использования на планшетах, так как она позволила мне:
-
Легко выявлять и оптимизировать узкие места производительности
-
Добавлять стили, специфичные для планшетов, без влияния на другие устройства
-
Реализовать состояния загрузки для улучшения мобильного опыта
-
Поддерживать чистое разделение между логикой игры и компонентами UI
Хорошо, теперь давайте начнем кодировать.
Пошаговое руководство по сборке
1. Настройка проекта
Настройка среды разработки
Чтобы начать с чистого проекта на React, откройте приложение терминала и выполните следующие команды (вы можете назвать папку вашего проекта как угодно – в моем случае имя ‘memory-card’):
npm create vite@latest memory-card -- --template react
cd memory-card
npm install
Установите необходимые зависимости
Единственными зависимостями, которые мы будем использовать в этом проекте, являются пакет хуков от UI.dev (кстати, здесь вы можете найти хорошо объясненную статью о том, как работает рендеринг в React).
Другой зависимостью является знаменитый CSS-препроцессор, SASS, который нам понадобится, чтобы иметь возможность писать наши CSS-модули в SASS вместо обычного CSS.
npm install @uidotdev/usehooks sass
Настройка Vite и проекта
При настройке нашего проекта нам нужно внести некоторые конкретные изменения в конфигурацию, чтобы справиться с предупреждениями SASS и улучшить наш опыт разработки. Вот как вы можете настроить 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, // Подавляет предупреждения о зависимостях SASS
charset: false // Предотвращает предупреждение о кодировке в последних версиях SASS
}
}
}
});
Имейте в виду, что большинство из этих конфигураций автоматически генерируются для вас, когда вы создаете проект с помощью Vite. Вот что происходит:
-
Настройка SASS:
-
quietDeps: true
: Это подавляет предупреждения о устаревших зависимостях в модулях SASS. Особенно полезно при работе с файлами SASS/SCSS сторонних разработчиков. -
charset: false
: Предотвращает предупреждение о “@charset”, которое появляется в новых версиях SASS при использовании специальных символов в ваших таблицах стилей.
-
-
Настройка тестирования:
-
globals: true
: Делает тестовые функции глобально доступными в файлах тестирования -
environment: 'jsdom'
: Предоставляет среду DOM для тестирования -
setupFiles
: Указывает на наш файл настройки тестирования
-
Эти настройки помогают создать более чистый опыт разработки, устраняя ненужные предупреждения в консоли, настраивая правильные конфигурации тестовой среды и обеспечивая плавную работу обработки SASS/SCSS.
Вы можете увидеть предупреждения в вашей консоли без этих конфигураций, когда:
-
Используете функции SASS/SCSS или импортируете файлы SASS
-
Запускаете тесты, требующие манипуляции с DOM
-
Используете специальные символы в ваших стилях
2. Создание компонентов
Создайте компонент Card
Сначала давайте создадим наш основной компонент карты, который будет отображать отдельные изображения:
// 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;
Компонент Card является основным строительным блоком нашей игры. Он отвечает за отображение отдельных изображений и обработку взаимодействий игроков. Давайте разберем его реализацию:
Разбор свойств:
-
image
: (строка)-
URL изображения, которое будет отображено и получено от нашего API-сервиса.
-
Он используется напрямую в атрибуте src тега img.
-
-
id
: (строка)-
Уникальный идентификатор для каждой карточки, который критически важен для отслеживания, какие карточки были нажаты.
-
Он передается в коллбек
processTurn
, когда карточка нажата.
-
-
category
: (строка) -
Описывает тип изображения (например, “аниме”, “неко”), и он используется в атрибуте alt для лучшей доступности.
-
Это помогает с SEO и экранными читалками.
-
processTurn
: (функция)-
Функция обратного вызова, переданная из родительского компонента, которая обрабатывает логику игры при клике на карту.
-
Он также управляет обновлением счета и изменениями состояния игры, а также определяет, была ли карта открыта ранее.
-
-
isLoading
: (логическое значение)-
Определяет, показывать ли состояние загрузки. Когда true, отображает компонент Loader вместо изображения.
Это улучшает пользовательский опыт во время загрузки изображения.
-
Стилизация компонента:
// 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%);
}
}
Использование в компоненте:
<Card
key={getKey()}
imgUrl={item?.image?.original?.url || ""}
imageId={item?.id}
categoryName={item?.category}
processTurn={(imageId) => processTurn(imageId)}
/>
Ключевые особенности:
-
Оптимизация производительности:
-
Использует
React.memo
, чтобы предотвратить ненужные повторные рендеры -
Реализует
useCallback
для обработчиков событий -
Управляет состоянием загрузки внутри компонента для лучшего UX
-
-
Управление состоянием загрузки:
-
Внутреннее состояние
isLoading
отслеживает загрузку изображения -
Показывает компонент Loader с сообщением во время загрузки
-
Скрывает изображение до полной загрузки, используя CSS-классы
-
-
Обработка событий:
-
handleImageLoad
: Управляет переходом состояния загрузки -
handleClick
: Обрабатывает ходы игрока через обратный вызовprocessTurn
-
Создание компонента CardsGrid
Это наш основной игровой компонент, который управляет состоянием игры, логикой подсчета очков и взаимодействиями с картами. Давайте разберем его реализацию:
// 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) {
// Управление состоянием
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);
// Пользовательский хук для получения изображений
const { data: fetchedData, fetchData, error } = useFetch();
// Обновление изображений при получении новых данных
useEffect(() => {
if (fetchedData?.images) {
setImages(fetchedData.images);
setIsLoading(false);
// Сброс нажатых изображений при загрузке новой партии
setClickedImages([]);
}
}, [fetchedData]);
// Вспомогательная функция для обновления лучшего результата
function updateBestScore(currentScore) {
if (currentScore > bestScore) {
setBestScore(currentScore);
}
}
// Основная логика игры
function processTurn(imageId) {
const newClickedImages = [...clickedImages, imageId];
setClickedImages(newClickedImages);
// Если нажать на одно и то же изображение дважды, сбросить всё
if (clickedImages.includes(imageId)) {
// Обновить лучший результат при необходимости
updateBestScore(score);
setClickedImages([]);
setScore(0);
} else {
// Обработать успешный выбор карты
const newScore = score + 1;
setScore(newScore);
// Проверить на идеальный результат (все карты нажаты один раз)
if (newClickedImages.length === images.length) {
updateBestScore(newScore);
fetchData();
setClickedImages([]);
} else {
// Перемешать изображения
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);
Стилизация компонента:
.container {
display: grid;
gap: 1rem 1rem;
grid-template-columns: auto; /* По умолчанию: один столбец для мобильных устройств */
background-color: #2196f3;
padding: 0.7rem;
cursor: pointer;
}
@media (min-width: 481px) {
.container {
grid-template-columns: auto auto; /* Два столбца для планшетов и выше */
}
}
@media (min-width: 769px) {
.container {
grid-template-columns: auto auto auto; /* Три столбца для настольных ПК и больших экранов */
}
}
Основные характеристики:
-
Управление состоянием:
-
Использует
useState
для состояния на уровне компонента -
Реализует
useLocalStorage
для постоянных игровых данных:-
clickedImages
: Отслеживает, какие карты были нажаты -
score
: Текущий счет игры -
bestScore
: Наивысший достигнутый счет
-
-
Управляет состоянием загрузки для получения изображений
-
Перетасовать карты
-
-
Логика игры:
-
processTurn
: Обрабатывает ходы игроков-
Отслеживает дублирующие клики
-
Обновляет очки
-
Управляет сценариями идеального счета
-
-
updateBestScore
: Обновляет рекорд, когда это необходимо -
Автоматически загружает новые изображения, когда раунд завершен
-
-
Получение данных:
-
Использует пользовательский
useFetch
хук для получения данных изображений -
Обрабатывает состояния загрузки и ошибок
-
Обновляет изображения, когда новые данные загружены
-
-
Оптимизация производительности:
-
Компонент обернут в
React.memo
-
Эффективные обновления состояния
-
Адаптивная сетка
-
-
Сохранение состояния:
-
Состояние игры сохраняется при перезагрузке страницы
-
Отслеживание лучших результатов
-
Сохранение текущего прогресса игры
-
Пример использования:
...
...
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;
Компонент CardsGrid служит сердцем нашей игры в карточки на память, управляя:
-
Состояние игры и логика
-
Отслеживание счета
-
Взаимодействие с картами
-
Загрузка и отображение изображений
-
Адаптивный макет
-
Сохранение данных
Эта реализация обеспечивает плавный игровой опыт, сохраняя читаемость кода и его поддерживаемость за счет четкого разделения задач и правильного управления состоянием.
3. Реализация уровня API
Наша игра использует надежный уровень API с несколькими резервными вариантами для обеспечения надежной доставки изображений. Давайте реализуем каждую службу и механизм резервирования.
Настройка основной службы API:
// 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 };
}
Создание первой резервной службы API:
// 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();
// Преобразование ответа в соответствии с нашим ожидаемым форматом
const transformedImages = result.results.map(item => ({
id: item.url.split('/').pop().split('.')[0], // Извлечение UUID из URL
image: {
original: {
url: item.url
}
},
artist: {
name: item.artist_name,
href: item.artist_href
},
source: item.source_url
}));
return { images: transformedImages };
}
Создание второй резервной службы API:
// 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();
// Преобразование ответа в соответствии с нашим ожидаемым форматом
const transformedImages = result.items.map(item => ({
id: item.id,
image: {
original: {
url: item.image_url
}
}
}));
return { images: transformedImages };
}
Создание механизма резервирования API:
// src/services/api/imageService.js
import { fetchNekosiaImages } from "./nekosiaApi";
import { fetchNekosImages } from "./nekosApi";
import { fetchNekosBestImages } from "./nekosBestApi";
export async function fetchImages() {
try {
// Сначала попробуйте основной API
return await fetchNekosiaImages();
} catch (error) {
console.warn("Primary API failed, trying fallback:", error);
// Попробуйте первый резервный API
try {
return await fetchNekosBestImages();
} catch (fallbackError) {
console.warn("First fallback API failed, trying second fallback:", fallbackError);
// Попробуйте второй резервный API
try {
return await fetchNekosImages();
} catch (secondFallbackError) {
console.error("All image APIs failed:", secondFallbackError);
throw new Error("All image APIs failed");
}
}
}
}
Используйте сервис изображений:
// 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,
};
}
Ключевые особенности нашей реализации API:
-
Несколько источников API:
-
Основной API (Nekosia): предоставляет изображения аниме высокого качества
-
Первый резервный (Nekos Best): включает информацию об артистах
-
Второй резервный (Nekos): простой и надежный резерв
-
-
Последовательный формат данных:
- Все API преобразуют свои ответы, чтобы соответствовать нашему ожидаемому формату:
{
images: [
{
id: string,
image: {
original: {
url: string
}
}
}
]
}
-
Надежная обработка ошибок:
-
Проверка ответов API
-
Проверка наличия допустимых URL изображений
-
Предоставление подробных сообщений об ошибках
-
Гибкий механизм отката
-
-
Функции безопасности:
-
Фильтрация безопасного контента (
рейтинг=безопасный
) -
Ограничение количества изображений (21 изображение)
-
Проверка URL
-
Проверка формата ответа
-
-
Предварительные соображения по производительности:
-
Оптимизированные размеры изображений
-
Теги фильтрации контента
-
Эффективное преобразование данных
-
Минимальное количество вызовов API
-
Эта реализация обеспечивает надежный источник изображений для нашей игры, одновременно аккуратно обрабатывая возможные сбои API. Последовательный формат данных во всех API облегчает переключение между ними без ущерба для функциональности игры.
Тестирование приложения
Тестирование является важной частью разработки любого приложения, и для нашей игры на запоминание карт мы внедрили комплексную стратегию тестирования, используя современные инструменты и практики. Давайте углубимся в то, как мы структурировали наши тесты и некоторые ключевые шаблоны тестирования, которые мы использовали.
Стек тестирования
-
Vitest: Наша основная тестовая платформа, выбранная за скорость и бесшовную интеграцию с Vite
-
React Testing Library: Для тестирования компонентов React с ориентированным на пользователя подходом
-
@testing-library/user-event: Для симуляции взаимодействий пользователей
-
jsdom: Для создания окружения DOM в наших тестах
Ключевые шаблоны тестирования
Тестирование было важной частью обеспечения надежности и поддерживаемости этой игры на память. Я реализовал всестороннюю стратегию тестирования, используя React Testing Library и Vitest, сосредоточив внимание на нескольких ключевых областях:
1. Тестирование компонентов
Я написал обширные тесты для своих компонентов React, чтобы убедиться, что они корректно отображаются и ведут себя ожидаемым образом. Например, компонент CardsGrid
, который является сердцем игры, имеет тщательное покрытие тестами, включая:
-
Начальные состояния рендеринга
-
Состояния загрузки
-
Обработка ошибок
-
Отслеживание очков
-
Поведение взаимодействия с картами
2. Мокирование тестов
Чтобы обеспечить надежные и быстрые тесты, я реализовал несколько стратегий мокирования:
-
Операции с локальным хранилищем с использованием хука useLocalStorage
-
Вызовы API с использованием хука
useFetch
-
Обработчики событий и обновления состояния
3. Лучшие практики тестирования
На протяжении реализации тестирования я следовал нескольким лучшим практикам:
-
Использование
beforeEach
иafterEach
хуков для сброса состояния между тестами -
Тестирование взаимодействия пользователей с помощью
fireEvent
из React Testing Library -
Написание тестов, которые соответствуют тому, как пользователи взаимодействуют с приложением
-
Тестирование как успешных, так и ошибочных сценариев
-
Изоляция тестов с использованием правильного мокирования
4. Инструменты тестирования
Проект использует современные инструменты и библиотеки для тестирования:
-
Vitest: В качестве тестового раннера
-
React Testing Library: Для тестирования React компонентов
-
@testing-library/jest-dom: Для улучшенных утверждений тестирования DOM
-
@testing-library/user-event: Для имитации взаимодействий пользователя
Этот комплексный подход к тестированию помог мне рано выявить ошибки, обеспечить качество кода и сделать рефакторинг более безопасным и управляемым.
Оптимизации
Чтобы обеспечить плавную работу, особенно на мобильных устройствах, мы внедрили несколько методов оптимизации:
-
Преобразование ответа
-
Стандартизированный формат данных для всех API
-
Эффективное извлечение ID из URL
-
Структурированные метаданные изображений для быстрого доступа
-
-
Оптимизация сети
-
Использование режима
no-cors
при необходимости для эффективного решения проблем CORS -
Обработка ошибок с использованием специфических кодов состояния для лучшего отладки
-
Последовательная структура ответа для всех реализаций API
-
-
Учет мобильных устройств в первую очередь
-
Оптимизированная стратегия загрузки изображений
-
Эффективная обработка ошибок для предотвращения ненужных повторных попыток
-
Упрощенная трансформация данных для снижения накладных расходов на обработку
-
Будущие улучшения
Есть несколько способов, которыми мы могли бы дополнительно улучшить этот проект:
-
Кэширование ответов API
-
Реализовать кэширование в локальном хранилище для часто используемых изображений
-
Добавить стратегию недействительности кэша для актуального контента
-
Реализовать прогрессивную загрузку изображений
-
-
Оптимизация производительности
-
Добавить ленивую загрузку изображений для лучшего времени начальной загрузки
-
Реализовать очередь запросов для лучшего управления пропускной способностью
-
Добавить сжатие ответов для более быстрого передачи данных
-
-
Улучшение надежности
-
Добавить проверку состояния API перед попытками
-
Реализовать механизмы повторных попыток с экспоненциальным увеличением времени ожидания
-
Добавить шаблон защитного отключения для неработающих API
-
-
Аналитика и мониторинг
-
Отслеживать уровень успеха API
-
Мониторить время ответа
-
Реализовать автоматическое переключение API на основе показателей производительности
-
Эта надежная реализация обеспечивает функциональность и производительность нашей игры даже в неблагоприятных сетевых условиях или при недоступности API, при этом оставляя место для будущих улучшений и оптимизаций.
Заключение
Создание этой карточной игры памяти стало не только созданием увлекательной, безрекламной альтернативы для детей — это было упражнением в реализации современных лучших практик веб-разработки при решении реальной проблемы.
Проект демонстрирует, как сочетание продуманной архитектуры, надежного тестирования и надежных механизмов резервного копирования может привести к приложению, готовому к производству, которое одновременно развлекает и обучает.
🗝️ Основные выводы
-
Разработка, ориентированная на пользователя
-
Началась с четкой проблемы (игры, перегруженные рекламой, влияющей на опыт пользователя)
-
Реализованы функции, улучшающие геймплей без прерываний
-
Сохранено внимание к производительности и надежности на всех устройствах
-
-
Техническое совершенство
-
Использование современных шаблонов и хуков React для чистого, поддерживаемого кода
-
Реализована комплексная стратегия тестирования, обеспечивающая надежность
-
Создана надежная система резервного API для бесперебойного геймплея
-
-
Производительность в первую очередь
-
Принят подход “мобильный в первую очередь” с адаптивным дизайном
-
Оптимизирована загрузка и обработка изображений
-
Реализовано эффективное управление состоянием и стратегии кэширования
-
📚 Результаты обучения
Этот проект демонстрирует, как казалось бы простые игры могут быть отличными инструментами для внедрения сложных технических решений. От архитектуры компонентов до резервного копирования API, каждая функция была разработана с учетом масштабируемости и поддерживаемости, доказывая, что даже хобби-проекты могут поддерживать качество кода на профессиональном уровне.
🔮 Движение вперед
Хотя игра успешно достигает своей главной цели — предоставить безрекламный, приятный опыт, задокументированные будущие улучшения предоставляют четкий план для эволюции. Будь то внедрение дополнительных оптимизаций или добавление новых функций, основа надежна и готова к расширению.
Игра “Memory Card Game” служит свидетельством того, как личные проекты могут как решать реальные проблемы, так и служить платформами для внедрения передовых практик в современной веб-разработке. Не стесняйтесь изучать код, вносить свой вклад или использовать его в качестве вдохновения для своих собственных проектов!
Source:
https://www.freecodecamp.org/news/how-to-build-a-memory-card-game-using-react/