أساسيات React.js
التكامل مع واجهات برمجة التطبيقات في React
التكامل مع واجهات برمجة التطبيقات في React
تعلم كيفية دمج واجهات REST API في تطبيقات React، والتعامل مع جلب البيانات غير المتزامن، وإدارة حالات التحميل والأخطاء، وتنفيذ ميزات متقدمة مثل الترقيم والتمرير اللانهائي.
جلب البيانات باستخدام Fetch API
Fetch API هي الطريقة الحديثة لإجراء طلبات HTTP في JavaScript. لنبدأ بمثال أساسي:
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(error => {
setError(error.message);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
ملاحظة: تعامل دائمًا مع حالات التحميل والأخطاء عند جلب البيانات. يوفر ذلك تجربة مستخدم أفضل ويساعد في تصحيح المشاكل.
استخدام Async/Await مع Fetch
صيغة Async/await تجعل الكود غير المتزامن أكثر قابلية للقراءة. إليك كيفية إعادة هيكلة المثال السابق:
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUsers(data);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
if (loading) {
return <div className="spinner">Loading...</div>;
}
if (error) {
return <div className="error">Error: {error}</div>;
}
return (
<div className="user-list">
{users.map(user => (
<div key={user.id} className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
);
}
نصيحة: استخدم كتلة finally للتأكد من تعيين حالة التحميل إلى false بغض النظر عن النجاح أو الفشل.
إنشاء Hook مخصص لجلب البيانات
استخرج منطق جلب البيانات إلى hook قابل لإعادة الاستخدام:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
setError(null);
} catch (error) {
setError(error.message);
setData(null);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// الاستخدام
function Users() {
const { data: users, loading, error } = useFetch(
'https://jsonplaceholder.typicode.com/users'
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
عمليات CRUD مع REST APIs
تنفيذ عمليات الإنشاء والقراءة والتحديث والحذف:
import { useState, useEffect } from 'react';
function PostManager() {
const [posts, setPosts] = useState([]);
const [newPost, setNewPost] = useState({ title: '', body: '' });
const [loading, setLoading] = useState(false);
// القراءة - جلب جميع المنشورات
useEffect(() => {
fetchPosts();
}, []);
const fetchPosts = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
setPosts(data.slice(0, 10)); // الحد من 10 منشورات
} catch (error) {
console.error('Error fetching posts:', error);
}
};
// الإنشاء - إضافة منشور جديد
const createPost = async (e) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newPost),
});
const data = await response.json();
setPosts([data, ...posts]);
setNewPost({ title: '', body: '' });
} catch (error) {
console.error('Error creating post:', error);
} finally {
setLoading(false);
}
};
// التحديث - تعديل منشور موجود
const updatePost = async (id, updatedData) => {
try {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${id}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updatedData),
}
);
const data = await response.json();
setPosts(posts.map(post => (post.id === id ? data : post)));
} catch (error) {
console.error('Error updating post:', error);
}
};
// الحذف - إزالة منشور
const deletePost = async (id) => {
try {
await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'DELETE',
});
setPosts(posts.filter(post => post.id !== id));
} catch (error) {
console.error('Error deleting post:', error);
}
};
return (
<div>
<form onSubmit={createPost}>
<input
type="text"
placeholder="العنوان"
value={newPost.title}
onChange={(e) => setNewPost({ ...newPost, title: e.target.value })}
required
/>
<textarea
placeholder="المحتوى"
value={newPost.body}
onChange={(e) => setNewPost({ ...newPost, body: e.target.value })}
required
/>
<button type="submit" disabled={loading}>
{loading ? 'جاري الإنشاء...' : 'إنشاء منشور'}
</button>
</form>
<div className="posts">
{posts.map(post => (
<div key={post.id} className="post-card">
<h3>{post.title}</h3>
<p>{post.body}</p>
<button onClick={() => deletePost(post.id)}>حذف</button>
</div>
))}
</div>
</div>
);
}
تحذير: تحقق دائمًا من صحة مدخلات المستخدم قبل إرسالها إلى API. فكر في تنفيذ معالجة أخطاء مناسبة وتعليقات المستخدم لجميع عمليات CRUD.
تنفيذ الترقيم
التعامل مع مجموعات البيانات الكبيرة باستخدام الترقيم:
import { useState, useEffect } from 'react';
function PaginatedPosts() {
const [posts, setPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [loading, setLoading] = useState(false);
const postsPerPage = 10;
useEffect(() => {
fetchPosts(currentPage);
}, [currentPage]);
const fetchPosts = async (page) => {
setLoading(true);
try {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=${postsPerPage}`
);
const data = await response.json();
const total = response.headers.get('X-Total-Count');
setPosts(data);
setTotalPages(Math.ceil(total / postsPerPage));
} catch (error) {
console.error('Error fetching posts:', error);
} finally {
setLoading(false);
}
};
const goToPage = (page) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
}
};
return (
<div>
{loading ? (
<div>جاري التحميل...</div>
) : (
<>
<div className="posts">
{posts.map(post => (
<div key={post.id} className="post-card">
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
</div>
<div className="pagination">
<button
onClick={() => goToPage(currentPage - 1)}
disabled={currentPage === 1}
>
السابق
</button>
<span>
صفحة {currentPage} من {totalPages}
</span>
<button
onClick={() => goToPage(currentPage + 1)}
disabled={currentPage === totalPages}
>
التالي
</button>
</div>
</>
)}
</div>
);
}
تنفيذ التمرير اللانهائي
تحميل المزيد من البيانات مع تمرير المستخدم لأسفل:
import { useState, useEffect, useRef, useCallback } from 'react';
function InfiniteScrollPosts() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const observer = useRef();
const lastPostRef = useCallback((node) => {
if (loading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setPage(prevPage => prevPage + 1);
}
});
if (node) observer.current.observe(node);
}, [loading, hasMore]);
useEffect(() => {
const fetchPosts = async () => {
setLoading(true);
try {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`
);
const data = await response.json();
setPosts(prevPosts => [...prevPosts, ...data]);
setHasMore(data.length > 0);
} catch (error) {
console.error('Error fetching posts:', error);
} finally {
setLoading(false);
}
};
fetchPosts();
}, [page]);
return (
<div>
<div className="posts">
{posts.map((post, index) => {
if (posts.length === index + 1) {
return (
<div ref={lastPostRef} key={post.id} className="post-card">
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
);
} else {
return (
<div key={post.id} className="post-card">
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
);
}
})}
</div>
{loading && <div className="loading">جاري تحميل المزيد...</div>}
{!hasMore && <div className="end">لا توجد منشورات أخرى</div>}
</div>
);
}
نصيحة: Intersection Observer API أكثر كفاءة من مستمعات أحداث التمرير لتطبيقات التمرير اللانهائي.
تمرين 1: أنشئ مكون قائمة منتجات يجلب البيانات من API، وينفذ وظيفة البحث، ويعرض حالات التحميل. قم بتضمين معالجة الأخطاء ومنطق إعادة المحاولة.
تمرين 2: قم ببناء نظام تعليقات مع عمليات CRUD. يجب أن يتمكن المستخدمون من إضافة وتعديل وحذف التعليقات. نفذ تحديثات واجهة المستخدم المتفائلة لتجربة مستخدم أفضل.
تمرين 3: أنشئ معرض صور بتمرير لانهائي يحمل الصور من Unsplash API. نفذ skeleton للتحميل وتعامل مع أخطاء تحديد المعدل بشكل جيد.