最近,當我看著我的孩子在平板電腦上玩免費記憶遊戲時,我注意到她在應付大量廣告和惱人的彈出式橫幅廣告時感到吃力。

這激發了我為她建立類似遊戲的靈感。由於她目前喜歡動漫,我決定使用可愛的動漫風格圖像來創建這個遊戲。

在本文中,我將帶您進入為自己或您的孩子建立遊戲的過程🎮。

我們將首先探索遊戲特色,然後涵蓋技術堆棧和項目結構——這兩者都很簡單。最後,我們將討論優化和確保在移動設備上流暢遊戲的問題📱。

如果您想跳過閱讀,這裡 💁 是 GitHub 存儲庫 🙌。而您可以在這裡看到實時演示。

目錄

項目描述

在這個教程中,我們將使用 React 構建一個具有挑戰性的記憶卡遊戲,測試您的記憶能力。您的目標是點擊獨特的動漫圖像,不能點擊相同的圖像兩次。每次獨特的點擊都會為您賺取積分,但請小心——點擊一個圖像兩次會重置您的進度。

遊戲特色:

  • 🎯 挑戰您記憶的動態遊戲玩法

  • 🔄 每次點擊後卡片重新洗牌增加難度

  • 🏆 記錄分數並保持最高分

  • 😺 The Nekosia API 提供可愛的動漫圖片

  • ✨ 流暢的加載過渡和動畫

  • 📱 響應式設計適用於所有設備

  • 🎨 乾淨、現代的用戶界面

這款遊戲將幫助您在享受可愛的動漫圖片的同時測試您的記憶技能。您能達到完美分數嗎?

如何玩

  1. 點擊任何卡片開始遊戲

  2. 記住您已經點擊的卡片

  3. 嘗試只點擊每張卡片一次

  4. 觀察您的分數隨著每次獨特選擇而增加

  5. 然後繼續玩遊戲,試著打破您的最佳分數

技術堆棧

以下是我們將使用的主要技術清單:

  • NPM– 用於管理JavaScript依賴項和項目腳本的包管理器。

  • Vite – 一個提供快速開發環境的構建工具,特別針對現代網頁專案進行優化。

  • React – 一個流行的 JavaScript 函式庫,用於構建用戶界面,實現高效的渲染和狀態管理。

  • CSS Modules – 一種樣式解決方案,將 CSS 限定於單個組件,防止樣式衝突並確保可維護性。

讓我們來建造這個遊戲

從這一點起,我將指導你了解我在建造這個遊戲時所遵循的過程。

專案結構和架構

在構建這個記憶卡遊戲時,我仔細組織了代碼庫,以確保可維護性、可擴展性,以及清晰的關注點分離。讓我們來探討這個結構及每個決策背後的理由:

基於組件的架構

我選擇了基於組件的架構有幾個原因:

  • 模組化: 每個組件都有自己的邏輯和樣式,獨立封裝

  • 可重用性: 像 CardLoader 這樣的組件可以在整個應用中重用

  • 可維護性: 更容易調試和修改個別組件

  • 測試: 可以獨立測試組件

組件組織

  1. 卡片組件
  • 因為它是核心遊戲元素,所以分開到自己的目錄中

  • 包含 JSX 和 SCSS 模組以進行封裝

  • 處理單個卡片的渲染、加載狀態和點擊事件

  1. 卡片網格組件
  • 管理遊戲板的佈局

  • 處理卡片的洗牌和分配

  • 控制不同屏幕尺寸的響應式網格佈局

  1. 加載器組件
  • 可重用的加載指示器

  • 改善圖像加載期間的用戶體驗

  • 可供任何需要加載狀態的組件使用

  1. 頭部/底部/副標題組件
  • 應用程序佈局的結構性組件

  • 頭部顯示遊戲標題和分數

  • 底部顯示版權和版本信息

  • 副標題提供遊戲說明

CSS 模塊化方法

我使用 CSS 模塊(.module.scss 文件)帶來多個好處:

  • 作用域化的樣式:避免組件之間的樣式泄漏

  • 名稱衝突:自動生成唯一的類名

  • 可維護性:樣式與其組件共存

  • SCSS 功能:利用 SCSS 功能保持樣式模塊化

自定義 Hooks

hooks 目錄包含像 useFetch 這樣的自定義 Hooks:

  • 關注點分離:隔離數據獲取邏輯

  • 重用性:可被任何需要圖像數據的組件使用

  • 狀態管理:處理加載、錯誤和數據狀態

  • 性能:實現像圖像大小控制這樣的優化

根級文件

App.jsx:

  • 作為應用程序的入口點

  • 管理全局狀態和路由(如果需要)

  • 協調組件組成

  • 處理頂級佈局

性能考量

該結構支持性能優化:

  • 代碼分割: 如有需要,組件可以延遲加載

  • 記憶化: 可有效地對組件進行記憶化

  • 樣式加載: CSS 模塊使樣式加載效率高

  • 資源管理: 圖片和資源被妥善組織

可擴展性

這種結構支持輕鬆擴展:

  • 新功能可以作為新組件添加

  • 可以為新功能創建附加鉤子

  • 隨著應用程式的增長,樣式仍然易於維護

  • 測試可以在任何層級實施

開發經驗

該結構增強了開發者的體驗:

  • 清晰的文件組織

  • 直觀的組件位置

  • 易於查找並修改特定功能

  • 支持高效的協作

當優化遊戲以適用於平板時,此架構尤其有價值,因為它使我能夠:

  1. 輕鬆識別和優化性能瓶頸

  2. 添加特定於平板的樣式而不影響其他設備

  3. 實現加載狀態以提供更好的移動體驗

  4. 保持遊戲邏輯和UI組件之間清晰的分離

好的,現在讓我們開始編碼。

逐步構建指南

1. 項目設置

設置開發環境

為了開始一個乾淨的 React 項目,打開您的終端機應用程序並運行以下命令(您可以根據自己的喜好命名項目文件夾 – 在我的情況下,名稱是 ‘memory-card’):

npm create vite@latest memory-card -- --template react
cd memory-card
npm install

安裝所需的依賴項

在此項目中我們將使用的唯一依賴項是來自 UI.dev 的 hook 套件(順便說一句,這裡您可以找到一篇關於 React 渲染工作原理的詳細文章)。

另一個依賴項是著名的 CSS 預處理器,SASS,我們將需要它來能夠使用 SASS 編寫我們的 CSS 模塊,而不是普通的 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 創建項目時,大多數這些配置都是為您自動生成的。這裡發生了什麼:

  1. SASS 配置:

    • quietDeps: true: 這將使 SASS 模組中關於已棄用依賴的警告靜音。在處理第三方 SASS/SCSS 檔案時特別有用。

    • charset: false: 防止在樣式表中使用特殊字符時出現新版本 SASS 的 “@charset” 警告。

  2. 測試配置:

    • globals: true: 使測試函數在測試檔案中全域可用

    • environment: 'jsdom': 為測試提供一個 DOM 環境

    • setupFiles: 指向我們的測試設置檔案

這些配置有助於創建更清潔的開發經驗,消除控制台中不必要的警告訊息,設定適當的測試環境配置,確保 SASS/SCSS 處理順利進行。

當您:

  • 使用 SASS/SCSS 功能或導入 SASS 檔案

  • 運行需要 DOM 操作的測試

  • 在樣式表中使用特殊字元

2. 構建元件

建立卡片元件

首先,讓我們創建基本的卡片元件,用於顯示個別圖片:

// 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;

卡片元件是我們遊戲的基本構建塊。它負責顯示個別圖片並處理玩家互動。讓我們來分解其實現方式:

道具拆解:

  1. 圖片: (字串)

    • 從我們的 API 服務收到的要顯示的圖片的 URL。

    • 直接用於 img 標籤的 src 屬性。

  2. 識別碼: (字串)

    • 每張卡片的唯一識別碼,對於追蹤被點擊的卡片至關重要。

    • 當卡片被點擊時,它將傳遞給 processTurn 回調函數。

  3. 類別: (字串)

    • 描述圖片的類型(例如,“動漫”,“貓咪”),並用於 alt 屬性以提高可訪問性。

    • 有助於 SEO 和螢幕閱讀器。

  4. processTurn:(函數)

    • 從父組件傳遞的回調函數,當點擊卡片時處理遊戲邏輯。

    • 它還管理分數更新和遊戲狀態變化,並確定卡片是否已被點擊。

  5. 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)} 
/>

主要功能:

  1. 性能優化

    • 使用React.memo來防止不必要的重新渲染

    • 為事件處理程序實現useCallback

    • 內部管理加載狀態以提高用戶體驗

  2. 加載狀態管理

    • 內部isLoading狀態跟踪圖像加載

    • 在加載時顯示帶消息的Loader組件

    • 使用CSS類隱藏圖像,直到完全加載

  3. 事件處理:

    • 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);

  // 用於獲取圖片的自定義 Hook
  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; /* 桌面及更大設備為三列 */
  }
}

主要功能分解:

  1. 狀態管理:

    • 使用 useState 來管理元件層級的狀態

    • 使用 useLocalStorage 來持久化遊戲數據:

      • clickedImages: 追蹤被點擊的卡片

      • score: 當前遊戲得分

      • bestScore: 最高得分

    • 管理圖片獲取的加載狀態

    • 洗牌卡片

  2. 遊戲邏輯

    • processTurn:處理玩家移動

      • 追蹤重複點擊

      • 更新分數

      • 管理完美分數情況

    • updateBestScore:必要時更新最高分

    • 完成一輪後自動抓取新圖片

  3. 資料抓取

    • 使用自訂的 useFetch hook 來獲取圖片數據

    • 處理加載和錯誤狀態

    • 在獲取新數據時更新圖片

  4. 性能優化

    • 組件包裹在React.memo

    • 高效的狀態更新

    • 響應式網格佈局

  5. 持久性

    • 遊戲狀態在重新加載頁面時持久存在

    • 最佳成績追蹤

    • 當前遊戲進度保存

使用示例:

...
...

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;

卡片網格組件作為我們記憶卡遊戲的核心,管理:

  • 遊戲狀態和邏輯

  • 得分追蹤

  • 卡片互動

  • 圖片加載和顯示

  • 響應式布局

  • 數據持久性

通過清晰分離關注點和適當的狀態管理,此實現提供了流暢的遊戲體驗,同時保持了代碼的可讀性和可維護性。

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], // 從 URL 提取 UUID
    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 实现的主要特点:

  1. 多个 API 来源

    • 主要 API(Nekosia):提供高质量的动漫图片

    • 第一个回退(Nekos Best):包含艺术家信息

    • 第二个回退(Nekos):简单可靠的备用

  2. 一致的数据格式

    • 所有 API 都会将它们的响应转换为我们期望的格式:
    {
      images: [
        {
          id: string,
          image: {
            original: {
              url: string
            }
          }
        }
      ]
    }
  1. 強大的錯誤處理:

    • 驗證API回應

    • 檢查有效的圖片URL

    • 提供詳細的錯誤消息

    • 優雅的後備機制

  2. 安全功能:

    • 安全內容過濾 (rating=safe)

    • 圖片數量限制(21張圖片)

    • URL驗證

    • 回應格式驗證

  3. 性能考量:

    • 優化的圖片大小

    • 過濾的內容標籤

    • 高效的數據轉換

    • 最小化的API呼叫

這個實作確保我們的遊戲在處理潛在的 API 失敗時有可靠的圖片來源。跨所有 API 的一致資料格式使得在不影響遊戲功能的情況下輕鬆切換之間。

測試應用程式

測試是任何應用程式開發的重要部分,在我們的記憶卡遊戲中,我們使用現代工具和實踐實施了全面的測試策略。讓我們深入研究我們如何組織測試和我們使用的一些關鍵測試模式。

測試堆疊

  • Vitest: 我們的主要測試框架,因其速度和與 Vite 的無縫集成而被選擇

  • React Testing Library: 用於以用戶為中心的方式測試 React 組件

  • @testing-library/user-event: 用於模擬用戶交互

  • jsdom: 用於在測試中創建 DOM 環境

關鍵測試模式

測試是確保此記憶卡遊戲的可靠性和可維護性的重要部分。我使用 React Testing Library 和 Vitest 實施了全面的測試策略,專注於幾個關鍵領域:

1. 元件測試

我為我的 React 元件撰寫了廣泛的測試,以確保它們能正確渲染並符合預期行為。例如,CardsGrid 元件是遊戲的核心,其完整的測試覆蓋包括:

  • 初始渲染狀態

  • 加載狀態

  • 錯誤處理

  • 分數追蹤

  • 卡片互動行為

2. 測試模擬

為了確保可靠且快速的測試,我實施了幾種模擬策略:

  • 使用 useLocalStorage hook 進行本地存儲操作

  • 使用 useFetch hook 進行 API 調用

  • 事件處理程序和狀態更新

3. 測試最佳實踐

在我的測試實施中,我遵循了幾項最佳實踐:

  • 使用 beforeEachafterEach 鉤子在測試之間重置狀態

  • 使用 fireEvent 從 React Testing Library 測試用戶交互

  • 編寫類似用戶與應用程序互動的測試

  • 測試成功和錯誤情況

  • 使用適當的模擬來進行測試隔離

4. 測試工具

該項目利用了現代測試工具和庫:

  • Vitest:作為測試運行器

  • React Testing Library:用於測試 React 組件

  • @testing-library/jest-dom:用於增強 DOM 測試斷言

  • @testing-library/user-event: 用於模擬用戶交互

這套全面的測試方法幫助我早期發現錯誤,確保代碼質量,使重構更安全、更易管理。

優化

為確保流暢性能,尤其是在移動設備上,我們實施了幾項優化技術:

  1. 響應轉換

    • 統一所有 API 中的數據格式

    • 從 URL 中高效提取 ID

    • 結構化的圖像元數據以便快速訪問

  2. 網絡優化

    • 在適當情況下使用 no-cors 模式來有效處理 CORS 問題

    • 具有特定狀態碼的錯誤處理以進行更好的調試

    • 在所有 API 實現中保持一致的響應結構

  3. 首要考慮因素:移動優先

    • 優化的圖片加載策略

    • 高效的錯誤處理以防止不必要的重試

    • 精簡的數據轉換以減少處理開銷

未來改進

有幾種方式可以進一步改進此項目:

  1. API響應緩存

    • 為常用圖像實施本地存儲緩存

    • 為新鮮內容添加緩存失效策略

    • 實施漸進式圖片加載

  2. 性能優化

    • 為更好的初始加載時間添加圖像惰性加載

    • 實現請求排隊以更好地管理帶寬

    • 為更快的數據傳輸添加響應壓縮

  3. 可靠性增強

    • 在嘗試之前添加 API 健康檢查

    • 使用指數遞增退避實現重試機制

    • 為失敗的 API 添加斷路器模式

  4. 分析和監控

    • 追踪 API 成功率

    • 監控響應時間

    • 根據性能指標實現自動切換 API

這種堅固的實現確保我們的遊戲即使在逆境網絡條件或 API 不可用的情況下仍然保持功能正常和高效,同時仍具有未來改進和優化的空間。

結論

構建這款記憶卡遊戲不僅僅是為孩子們創建一個有趣、無廣告的替代方案,更是一個實踐現代網頁開發最佳實踐並解決現實問題的過程。

該項目展示了如何結合周到的架構、堅固的測試和可靠的回退機制,可以得到一個既有趣又有教育意義的可投產應用程式。

🗝️ 關鍵要點

  1. 以用戶為中心的開發

    • 從明確問題開始(廣告填充的遊戲影響了用戶體驗)

    • 實施增強遊戲性並且不中斷的功能

    • 在各種設備上保持性能和可靠性的專注

  2. 技術卓越

    • 利用現代的React模式和hooks編寫乾淨、易於維護的代碼

    • 實施全面的測試策略,確保可靠性

    • 為遊戲的連貫性創建強大的API回退系統

  3. 性能至上

    • 採用了響應式設計的移動優先方法

    • 優化了圖片加載和處理

    • 實現了高效的狀態管理和緩存策略

📚 學習成果

這個項目展示了看似簡單的遊戲如何成為實現複雜技術解決方案的絕佳工具。從組件架構到 API 回退,每個功能都是以可擴展性和可維護性為設計原則,證明即使是愛好項目也可以保持專業代碼質量。

🔮 展望未來

儘管這款遊戲成功實現了提供無廣告、愉快的體驗的主要目標,但所記錄的未來改進提供了進化的清晰路線圖。無論是實施額外優化還是添加新功能,基礎穩固且準備好擴展。

這個記憶卡遊戲展示了個人項目如何既解決現實問題,又作為實施現代網頁開發最佳實踐的平台。歡迎探索代碼,貢獻,或將其作為啟發來開展你自己的項目!