مؤخرًا، بينما كنت أشاهد طفلتي 🧒🏻 وهي تلعب ألعاب الذاكرة المجانية على جهازها اللوحي، لاحظت أنها تُعاني من عدد كبير جدًا من الإعلانات والبنرات المزعجة.
هذا دفعني لبناء لعبة مماثلة لها. نظرًا لاهتمامها الحالي بالأنمي، قررت إنشاء اللعبة باستخدام صور أنمي لطيفة.
في هذه المقالة، سأقدم لك خطوات بناء اللعبة لنفسك أو لأطفالك 🎮.
سنبدأ باستكشاف ميزات اللعبة، ثم سنغطي التكنولوجيا وهيكل المشروع، كلاهما بسيط. وأخيرًا، سنناقش الأمور المتعلقة بالتحسينات وضمان سلاسة اللعب على الأجهزة المحمولة 📱.
إذا كنت ترغب في تخطي القراءة، يمكنك النقر هنا 💁 للوصول إلى مستودع GitHub 🙌. ويمكنك مشاهدة النسخة التجريبية عبر النقر هنا.
جدول المحتويات
وصف المشروع
في هذا البرنامج التعليمي، سنقوم ببناء لعبة بطاقات ذاكرة تحديّة باستخدام React تختبر قدرات استدعائك. هدفك هو النقر على صور أنمي فريدة من نوعها دون النقر على نفس الصورة مرتين. كل نقرة فريدة تكسبك نقاطًا، ولكن كن حذرًا – إعادة النقر على صورة مرتين تُعيد تقدمك.
ميزات اللعبة:
-
🎯 أسلوب لعب ديناميكي يتحدى ذاكرتك
-
🔄 تقليب البطاقات بعد كل نقرة لزيادة الصعوبة
-
🏆 تتبع النقاط مع الاحتفاظ بأفضل نقاطك
-
😺 صور أنمي لطيفة من واجهة برمجة التطبيقات نيكوسيا
-
✨ انتقالات ورسوم متحركة سلسة
-
📱 تصميم متجاوب لجميع الأجهزة
-
🎨 واجهة مستخدم نظيفة وحديثة
سيساعدك اللعبة على اختبار مهارات الذاكرة الخاصة بك أثناء الاستمتاع بصور أنمي لطيفة. هل يمكنك تحقيق النتيجة المثالية؟
كيفية اللعب
-
انقر على أي بطاقة للبدء
-
تذكر البطاقات التي قمت بالنقر عليها
-
حاول النقر على جميع البطاقات مرة واحدة تمامًا
-
شاهد نتيجتك تزداد مع كل اختيار فريد
-
ثم استمر في اللعب لمحاولة تحقيق أفضل نتيجة لديك
التقنيات المستخدمة
فيما يلي قائمة بالتقنيات الرئيسية التي سنستخدمها:
-
إن بي إم – منسق حزم للجافا سكريبت يساعد في إدارة التبعيات والنصوص للمشروع.
-
فايت (Vite) – أداة بناء توفر بيئة تطوير سريعة، مُحسّنة بشكل خاص لمشاريع الويب الحديثة.
-
ريأكت (React) – مكتبة JavaScript شائعة لبناء واجهات المستخدم، مما يمكّن من تقديم فعال وإدارة الحالة.
-
وحدات CSS – حل لتنسيق الأنماط يحدد CSS للمكونات الفردية، مما يمنع تعارض الأنماط ويضمن القابلية للصيانة.
لنبدأ ببناء اللعبة
من هذه النقطة فصاعدًا، سأرشدك خلال العملية التي اتبعتها عند بناء هذه اللعبة.
هيكل المشروع والعمارة
عند بناء لعبة بطاقة الذاكرة هذه، نظمت قاعدة الشيفرة بعناية لضمان القابلية للصيانة، والقابلية للتوسع، والفصل الواضح للمسؤوليات. دعنا نستكشف الهيكل والتفكير وراء كل قرار:
العمارة المعتمدة على المكونات
اخترت عمارة معتمدة على المكونات لعدة أسباب:
-
التعددية: كل جزء يكون معزولًا بذاته بمنطقه وأنماطه الخاصة
-
إعادة الاستخدام: يمكن إعادة استخدام مكونات مثل
Card
وLoader
عبر التطبيق -
سهولة الصيانة: من الأسهل تصحيح وتعديل المكونات الفردية
-
الاختبار: يمكن اختبار المكونات بشكل منفصل
تنظيم المكونات
- مكون البطاقة
-
مفصول إلى دليله الخاص لأنه عنصر لعبة أساسي
-
يحتوي على وحدات JSX و SCSS للتقسيم
-
يدير عملية عرض البطاقة الفردية وحالات التحميل وأحداث النقر
- مكون شبكة البطاقات
-
يدير تخطيط لوحة اللعب
-
يدير خلط وتوزيع البطاقات
-
يتحكم في تصميم الشبكة المتجاوب لأحجام الشاشات المختلفة
- مكون المحمل
-
مؤشر تحميل قابل لإعادة الاستخدام
-
يحسن تجربة المستخدم أثناء تحميل الصور
-
يمكن استخدامه من قبل أي مكون يحتاج إلى حالات تحميل
- مكونات الرأس/التذييل/العنوان الفرعي
-
مكونات هيكلية لتصميم التطبيق
-
يعرض الرأس عنوان اللعبة والنتائج
-
يعرض التذييل معلومات حقوق النشر والإصدار
-
يوفر العنوان الفرعي تعليمات اللعبة
أسلوب وحدات CSS
استخدمت وحدات CSS (.module.scss
ملفات) للعديد من الفوائد:
-
تخصيص الأنماط: يمنع تسرب الأنماط بين المكونات
-
تداخل الأسماء: ينشئ تلقائيًا أسماء فئات فريدة
-
قابلية الصيانة: يتم وضع الأنماط بجوار مكوناتها
-
ميزات SCSS: يستفيد من ميزات SCSS مع الحفاظ على الأنماط وحدة نمطية
خطافات مخصصة
دليل hooks
يحتوي على خطافات مخصصة مثل useFetch:
-
فصل الاهتمامات: يعزل منطق جلب البيانات
-
إعادة الاستخدام: يمكن استخدامه من قبل أي مكون يحتاج إلى بيانات الصور
-
إدارة الحالة: يتعامل مع حالات التحميل، والخطأ، والبيانات
-
الأداء: ينفذ تحسينات مثل التحكم في حجم الصور
ملفات المستوى الجذري
App.jsx:
-
يعمل كنقطة دخول للتطبيق
-
يدير الحالة العالمية والتوجيه (إذا لزم الأمر)
-
ينسق تكوين المكونات
-
يتعامل مع التخطيطات العلوية
اعتبارات الأداء
تدعم الهيكلة تحسينات الأداء:
-
تقسيم الشيفرة: يمكن تحميل المكونات عند الحاجة
-
التخزين المؤقت: يمكن تخزين المكونات بشكل فعال
-
تحميل الأنماط: تتيح وحدات CSS تحميل الأنماط بكفاءة
-
إدارة الأصول: يتم تنظيم الصور والموارد بشكل صحيح
قابلية التوسع
تتيح هذه الهيكلة إمكانية التوسع بسهولة:
-
يمكن إضافة ميزات جديدة كمكونات جديدة
-
يمكن إنشاء إضافات جديدة لوظائف جديدة
-
تظل الأنماط قابلة للصيانة مع نمو التطبيق
-
يمكن تنفيذ الاختبار في أي مستوى
تجربة التطوير
الهيكل يعزز تجربة المطور:
-
تنظيم واضح للملفات
-
مواقع العناصر بديهية
-
سهولة في العثور على وتعديل الميزات الخاصة
-
يدعم التعاون الفعال
ثبتت قيمة هذا النمط بشكل خاص عند تحسين اللعبة لاستخدام الأجهزة اللوحية، حيث سمح لي بـ:
-
تحديد وتحسين نقاط ضعف الأداء بسهولة
-
إضافة أنماط خاصة بالأجهزة اللوحية دون التأثير على الأجهزة الأخرى
-
تنفيذ حالات التحميل لتحسين تجربة الجوال
-
الحفاظ على فصل واضح بين منطق اللعبة وعناصر واجهة المستخدم
حسنًا، الآن دعونا نبدأ بالبرمجة.
دليل البناء خطوة بخطوة
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 // يمنع تحذير charset في إصدارات 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. بناء المكونات
إنشاء مكون البطاقة
أولاً، دعنا نقوم بإنشاء مكون البطاقة الأساسي الذي سيعرض الصور الفردية:
// 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;
مكون البطاقة هو كتلة بناء أساسية في لعبتنا. إنه مسؤول عن عرض الصور الفردية ومعالجة تفاعلات اللاعب. دعنا نقسم تنفيذه:
تفكيك الخصائص:
-
صورة
: (سلسلة نصية)-
رابط الصورة التي سيتم عرضها والتي تم تلقيها من خدمة الواجهة البرمجية لدينا.
-
يُستخدم مباشرة في سمة src لعنصر الصورة.
-
-
معرف
: (سلسلة نصية)-
معرف فريد لكل بطاقة يعد أمرًا حيويًا لتتبع البطاقات التي تم النقر عليها.
-
يتم تمريره إلى تابع
processTurn
عندما يتم النقر على بطاقة.
-
-
فئة
: (سلسلة نصية)-
يصف نوع الصورة (على سبيل المثال، “أنمي”، “نيكو”)، ويُستخدم في سمة alt لتحسين إمكانية الوصول.
-
يساعد في تحسين محركات البحث وقارئي الشاشة.
-
-
processTurn
: (وظيفة)-
وظيفة انتدابية تمرر من المكون الأصلي تدير منطق اللعبة عند النقر على بطاقة.
-
كما يدير تحديثات النقاط وتغييرات حالة اللعبة ويحدد ما إذا تم النقر على بطاقة من قبل.
-
-
isLoading
: (بولياني)-
يتحكم في ما إذا كان يجب عرض حالة التحميل. عندما يكون صحيحًا، يعرض مكون 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
لمعالجي الأحداث -
يدير حالة التحميل داخليًا لتحسين تجربة المستخدم
-
-
إدارة حالة التحميل:
-
الحالة الداخلية
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)
تستخدم لعبتنا طبقة واجهة برمجة قوية مع خيارات احتياطية متعددة لضمان توصيل الصور بموثوقية. دعنا نقوم بتنفيذ كل خدمة وآلية الاحتياطية.
إعداد خدمة الواجهة البرمجية الأساسية:
// 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 };
}
إنشاء أول خدمة واجهة برمجية احتياطية:
// 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 };
}
إنشاء الخدمة الواجهية البرمجية الاحتياطية الثانية:
// 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 };
}
قم ببناء آلية الاسترجاع البديلة للواجهة البرمجية:
// src/services/api/imageService.js
import { fetchNekosiaImages } from "./nekosiaApi";
import { fetchNekosImages } from "./nekosApi";
import { fetchNekosBestImages } from "./nekosBestApi";
export async function fetchImages() {
try {
// حاول الواجهة البرمجية الأساسية أولاً
return await fetchNekosiaImages();
} catch (error) {
console.warn("Primary API failed, trying fallback:", error);
// حاول الواجهة البرمجية البديلة الأولى
try {
return await fetchNekosBestImages();
} catch (fallbackError) {
console.warn("First fallback API failed, trying second fallback:", fallbackError);
// حاول الواجهة البرمجية البديلة الثانية
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,
};
}
الميزات الرئيسية لتنفيذ واجهة البرمجة الخاصة بنا:
-
مصادر واجهة برمجة التطبيقات المتعددة:
-
الواجهة البرمجية الأساسية (نيكوسيا): توفر صورًا أنمي عالية الجودة
-
الاسترجاع البديل الأول (نيكوس بيست): يتضمن معلومات الفنان
-
الاسترجاع البديل الثاني (نيكوس): نسخة احتياطية بسيطة وموثوقة
-
-
تنسيق بيانات متسق:
- تقوم جميع واجهات البرمجة بتحويل استجاباتها لتتوافق مع التنسيق المتوقع لدينا:
{
images: [
{
id: string,
image: {
original: {
url: string
}
}
}
]
}
-
معالجة الأخطاء القوية:
-
يتحقق من ردود الاستجابة لواجهة برمجة التطبيقات
-
يتحقق من صحة عناوين الصور
-
يوفر رسائل خطأ مفصلة
-
آلية تراجع متينة
-
-
ميزات الأمان:
-
تصفية المحتوى بشكل آمن (
rating=safe
) -
تحديد عدد الصور (21 صورة)
-
التحقق من الروابط
-
التحقق من تنسيق الاستجابة
-
-
اعتبارات الأداء:
-
أحجام صور محسنة
-
علامات تصفية المحتوى
-
تحويل البيانات بكفاءة
-
الحد الأدنى لمكالمات واجهة برمجة التطبيقات
-
تضمن هذا التنفيذ وجود مصدر موثوق لصور اللعبة لدينا مع التعامل اللطيف مع فشل واجهة برمجة التطبيقات المحتمل. يجعل التنسيق البيانات الثابت عبر جميع واجهات برمجة التطبيقات سهل التبديل بينها دون التأثير على وظائف اللعبة.
اختبار التطبيق
الاختبار هو جزء حاسم من تطوير أي تطبيق، وللعبة بطاقات الذاكرة لدينا، قمنا بتنفيذ استراتيجية اختبار شاملة باستخدام أدوات وممارسات حديثة. دعنا نتعمق في كيفية تنظيم اختباراتنا وبعض أنماط الاختبار الرئيسية التي استخدمناها.
مكدس الاختبارات
-
Vitest: إطار الاختبارات الرئيسي لدينا، اختير لسرعته وتكامله السلس مع Vite
-
مكتبة اختبار React: لاختبار مكونات React بنهج موجه للمستخدم
-
@testing-library/user-event: لمحاكاة تفاعلات المستخدم
-
jsdom: لإنشاء بيئة DOM في اختباراتنا
أنماط الاختبار الرئيسية
كان الاختبار جزءًا أساسيًا من ضمان موثوقية وصيانة لعبة بطاقة الذاكرة هذه. لقد قمت بتنفيذ استراتيجية اختبار شاملة باستخدام مكتبة اختبار React و Vitest، مع التركيز على عدة مجالات رئيسية:
1. اختبار المكونات
كتبت اختبارات شاملة لمكونات React الخاصة بي لضمان أنها تعرض بشكل صحيح وتتصرف كما هو متوقع. على سبيل المثال، مكون CardsGrid
، الذي هو قلب اللعبة، لديه تغطية اختبار شاملة تشمل:
-
حالات العرض الأولية
-
حالات التحميل
-
معالجة الأخطاء
-
تتبع النقاط
-
سلوك تفاعل البطاقة
2. تقليد الاختبار
لضمان اختبارات موثوقة وسريعة، نفذت عدة استراتيجيات تقليد:
-
عمليات التخزين المحلي باستخدام hook useLocalStorage
-
استدعاءات API باستخدام hook
useFetch
-
معالجي الأحداث وتحديثات الحالة
3. أفضل الممارسات في الاختبار
طوال تنفيذ اختباري، اتبعت عدة ممارسات جيدة:
-
استخدام خطوط
beforeEach
وafterEach
لإعادة تعيين الحالة بين الاختبارات -
اختبار تفاعلات المستخدم باستخدام
fireEvent
من مكتبة اختبار React -
كتابة اختبارات تشبه كيفية تفاعل المستخدمين مع التطبيق
-
اختبار السيناريوهات الناجحة والخطأ
-
عزل الاختبارات باستخدام عمليات افتراضية صحيحة
4. أدوات الاختبار
يستفيد المشروع من أدوات ومكتبات اختبار حديثة:
-
Vitest: كمشغل الاختبار
-
مكتبة اختبار React: لاختبار مكونات React
-
@testing-library/jest-dom: لتحسين تأكيدات اختبار DOM
-
@testing-library/user-event: لتحاكي تفاعلات المستخدم
هذا النهج الشامل للاختبارات ساعدني في اكتشاف الأخطاء مبكرًا، وضمان جودة الكود، وجعل عملية إعادة الترتيب أكثر أمانًا وإدارةً
التحسينات
لضمان أداء سلس، خاصة على الأجهزة المحمولة، قمنا بتنفيذ عدة تقنيات تحسين:
-
تحويل الاستجابة
-
تنسيق البيانات الموحد عبر جميع واجهات برمجة التطبيقات
-
استخراج معرف فعال من عناوين URL
-
بيانات الصور المنظمة للوصول السريع
-
-
تحسين الشبكة
-
استخدام وضع
no-cors
عند الضرورة لمعالجة مشاكل CORS بكفاءة -
معالجة الأخطاء برمز الحالة المحددة لتصحيح الأخطاء بشكل أفضل
-
هيكل الاستجابة المتسق عبر جميع تنفيذات واجهة برمجة التطبيقات
-
-
الاعتبارات أولاً في التصميم للهواتف المحمولة
-
استراتيجية تحميل الصور المحسنة
-
معالجة الأخطاء بكفاءة لمنع إعادة المحاولات غير الضرورية
-
تحويل البيانات بشكل مبسط لتقليل العبء الناتج عن المعالجة
-
تحسينات مستقبلية
هناك بعض الطرق التي يمكننا من خلالها تحسين هذا المشروع بشكل أكبر:
-
تخزين استجابة واجهة البرمجة التطبيقية
-
تنفيذ تخزين محلي للصور المستخدمة بشكل متكرر
-
إضافة استراتيجية إلغاء التخزين المؤقت للحصول على محتوى جديد
-
تنفيذ تحميل تدريجي للصور
-
-
تحسينات الأداء
-
إضافة تحميل كسلي للصور لتحسين وقت التحميل الأولي
-
تنفيذ قائمة انتظار الطلبات لإدارة النطاق الترددي بشكل أفضل
-
إضافة ضغط الاستجابة لنقل البيانات بشكل أسرع
-
-
تعزيزات الاعتمادية
-
إضافة فحص صحة واجهة برمجة التطبيقات قبل المحاولات
-
تنفيذ آليات إعادة المحاولة بالتراجع التقديمي
-
إضافة نمط الفاصل الكهربائي للواجهات البرمجية التي تفشل
-
-
تحليلات ورصد
-
تتبع معدلات نجاح واجهة برمجة التطبيقات
-
رصد أوقات الاستجابة
-
قم بتنفيذ تبديل تلقائي لواجهات برمجة التطبيقات استنادًا إلى مقاييس الأداء
-
تضمن هذه العملية القوية أن يظل لعبتنا قابلة للتشغيل وفعالة حتى في ظروف الشبكة السيئة أو عدم توفر واجهة برمجة التطبيقات، مع الحفاظ على مساحة للتحسينات والتحسينات المستقبلية.
الاستنتاج
كان بناء لعبة بطاقات الذاكرة هذه أكثر من مجرد إنشاء بديل ممتع خالٍ من الإعلانات للأطفال – بل كانت ممارسة لتنفيذ ممارسات تطوير الويب الحديثة بينما نحل مشكلة في العالم الحقيقي.
يظهر المشروع كيف يمكن أن تؤدي مزيجًا من التعميم المتأني والاختبار القوي والآليات الموثوقة للتعويض إلى تطبيق جاهز للإنتاج يكون مسليًا وتعليميًا على حد سواء.
🗝️ النقاط الرئيسية
-
تطوير مركز على المستخدم
-
بدأت بمشكلة واضحة (الألعاب المليئة بالإعلانات تؤثر على تجربة المستخدم)
-
تم تنفيذ ميزات تعزز تجربة اللعب دون انقطاعات
-
تم الحفاظ على التركيز على الأداء والموثوقية عبر الأجهزة
-
-
التفوق التقني
-
تم الاستفادة من أنماط هوك الحديثة في React للحصول على كود نظيف وقابل للصيانة
-
تم تنفيذ استراتيجية اختبار شاملة لضمان الموثوقية
-
تم إنشاء نظام قوي للاحتياطي API لضمان تجربة لعب دون انقطاعات
-
-
الأداء أولاً
-
تم اعتماد نهج أول للهواتف المحمولة مع تصميم متجاوب
-
تم تحسين تحميل الصور ومعالجتها
-
تم تنفيذ استراتيجيات إدارة الحالة والتخزين المؤقت بكفاءة
-
📚 نتائج التعلم
يظهر هذا المشروع كيف يمكن للألعاب التي تبدو بسيطة أن تكون وسائل ممتازة لتنفيذ حلول تقنية معقدة. من هيكل المكونات إلى البدائل في واجهة برمجة التطبيقات، تم بناء كل ميزة مع مراعاة القابلية للتوسع والصيانة، مما يثبت أن حتى المشاريع الهواية يمكن أن تحافظ على جودة كود بمستوى احترافي.
🔮 المضي قدماً
بينما يحقق اللعبة هدفه الأساسي المتمثل في تقديم تجربة خالية من الإعلانات وممتعة، فإن التحسينات المستقبلية الموثقة تقدم خارطة طريق واضحة للتطور. سواء كان ذلك بتنفيذ تحسينات إضافية أو إضافة ميزات جديدة، فإن الأساس قوي وجاهز للتوسع.
تقف لعبة بطاقات الذاكرة كشهادة على كيف يمكن للمشاريع الشخصية حلاً للمشاكل العملية وتوفير منصات لتطبيق أفضل الممارسات في تطوير الويب الحديث. لا تتردد في استكشاف الشيفرة، والمساهمة، أو استخدامها كمصدر إلهام لمشاريعك الخاصة!
Source:
https://www.freecodecamp.org/news/how-to-build-a-memory-card-game-using-react/