Apollo Client المتقدم
تقنيات Apollo Client المتقدمة
بعد الاستعلامات الأساسية، يوفر Apollo Client ميزات قوية للتحويرات وإدارة ذاكرة التخزين المؤقت وواجهة المستخدم المتفائلة والاستعلامات البطيئة والاشتراكات والحالة المحلية. أتقن هذه التقنيات لبناء تطبيقات GraphQL سريعة الاستجابة وفعالة للغاية.
خطاف useMutation
ينفذ خطاف useMutation تحويرات GraphQL لإنشاء البيانات أو تحديثها أو حذفها.
import React, { useState } from 'react';
import { useMutation, gql } from '@apollo/client';
const CREATE_POST = gql`
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
content
createdAt
}
}
`;
function CreatePostForm() {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [createPost, { data, loading, error }] = useMutation(CREATE_POST);
const handleSubmit = async (e) => {
e.preventDefault();
try {
await createPost({
variables: { title, content }
});
// مسح النموذج عند النجاح
setTitle('');
setContent('');
alert('تم إنشاء المنشور بنجاح!');
} catch (err) {
console.error('خطأ في التحوير:', err);
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="العنوان"
required
/>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="المحتوى"
required
/>
<button type="submit" disabled={loading}>
{loading ? 'جارٍ الإنشاء...' : 'إنشاء منشور'}
</button>
{error && <p>خطأ: {error.message}</p>}
</form>
);
}
export default CreatePostForm;
تحديثات ذاكرة التخزين المؤقت بعد التحويرات
بعد التحوير، تحتاج إلى تحديث ذاكرة التخزين المؤقت Apollo بحيث تعكس واجهة المستخدم التغييرات دون إعادة الجلب.
import { useMutation, gql } from '@apollo/client';
const CREATE_POST = gql`
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
content
}
}
`;
const GET_POSTS = gql`
query GetPosts {
posts {
id
title
content
}
}
`;
function CreatePostForm() {
const [createPost] = useMutation(CREATE_POST, {
update(cache, { data: { createPost } }) {
// قراءة المنشورات الموجودة من ذاكرة التخزين المؤقت
const existingPosts = cache.readQuery({ query: GET_POSTS });
// كتابة المنشورات المحدثة مرة أخرى إلى ذاكرة التخزين المؤقت
cache.writeQuery({
query: GET_POSTS,
data: {
posts: [...existingPosts.posts, createPost]
}
});
}
});
// تنفيذ النموذج...
}
const [deletePost] = useMutation(DELETE_POST, {
refetchQueries: [
{ query: GET_POSTS },
{ query: GET_USER_POSTS, variables: { userId: user.id } }
]
});
// أو استخدم رد اتصال refetchQueries
const [updatePost] = useMutation(UPDATE_POST, {
refetchQueries: (result) => [
{ query: GET_POST, variables: { id: result.data.updatePost.id } }
]
});
واجهة المستخدم المتفائلة
واجهة المستخدم المتفائلة تحدث ذاكرة التخزين المؤقت فورًا (قبل استجابة الخادم) لجعل التطبيق يبدو فوريًا. إذا فشل التحوير، يعيد Apollo التغيير.
import { useMutation, gql } from '@apollo/client';
const LIKE_POST = gql`
mutation LikePost($postId: ID!) {
likePost(postId: $postId) {
id
likes
isLiked
}
}
`;
function LikeButton({ post }) {
const [likePost] = useMutation(LIKE_POST, {
optimisticResponse: {
likePost: {
__typename: 'Post',
id: post.id,
likes: post.likes + 1,
isLiked: true
}
}
});
return (
<button onClick={() => likePost({ variables: { postId: post.id } })}>
{post.isLiked ? 'إلغاء الإعجاب' : 'إعجاب'} ({post.likes})
</button>
);
}
- استخدم للعمليات السريعة ومنخفضة المخاطر (الإعجابات، المتابعات، التبديلات)
- اجعل بنية الاستجابة المتفائلة متطابقة مع الاستجابة الفعلية
- قم بتضمين
__typenameفي الاستجابة المتفائلة لتطبيع ذاكرة التخزين المؤقت - تجنب العمليات الحرجة (المدفوعات، حذف البيانات)
خطاف useLazyQuery
بخلاف useQuery الذي ينفذ على الفور، يعيد useLazyQuery دالة يمكنك استدعاؤها يدويًا عند الحاجة.
import React, { useState } from 'react';
import { useLazyQuery, gql } from '@apollo/client';
const SEARCH_USERS = gql`
query SearchUsers($query: String!) {
searchUsers(query: $query) {
id
name
email
avatar
}
}
`;
function UserSearch() {
const [query, setQuery] = useState('');
// يعيد [executeQuery, { data, loading, error }]
const [searchUsers, { data, loading, error }] = useLazyQuery(SEARCH_USERS);
const handleSearch = (e) => {
e.preventDefault();
searchUsers({ variables: { query } });
};
return (
<div>
<form onSubmit={handleSearch}>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="البحث عن المستخدمين..."
/>
<button type="submit">بحث</button>
</form>
{loading && <p>جارٍ البحث...</p>}
{error && <p>خطأ: {error.message}</p>}
{data && (
<ul>
{data.searchUsers.map(user => (
<li key={user.id}>
<img src={user.avatar} alt={user.name} />
{user.name} - {user.email}
</li>
))}
</ul>
)}
</div>
);
}
export default UserSearch;
خطاف useSubscription
يشترك خطاف useSubscription في التحديثات في الوقت الفعلي عبر WebSocket.
import React from 'react';
import { useSubscription, gql } from '@apollo/client';
const MESSAGE_SUBSCRIPTION = gql`
subscription OnMessageAdded($channelId: ID!) {
messageAdded(channelId: $channelId) {
id
content
author {
id
name
avatar
}
createdAt
}
}
`;
function ChatMessages({ channelId, messages }) {
const { data, loading } = useSubscription(MESSAGE_SUBSCRIPTION, {
variables: { channelId },
onData: ({ client, data }) => {
// معالجة رسالة جديدة
console.log('رسالة جديدة:', data.data.messageAdded);
}
});
return (
<div>
{messages.map(msg => (
<div key={msg.id}>
<strong>{msg.author.name}:</strong> {msg.content}
</div>
))}
{loading && <p>جارٍ الاتصال بالدردشة...</p>}
</div>
);
}
export default ChatMessages;
إدارة الحالة المحلية
يمكن لـ Apollo Client إدارة الحالة المحلية جنبًا إلى جنب مع البيانات البعيدة، مما يلغي الحاجة إلى مكتبات إدارة حالة منفصلة.
import { makeVar, useReactiveVar } from '@apollo/client';
// إنشاء متغير تفاعلي
export const isLoggedInVar = makeVar(false);
export const currentUserVar = makeVar(null);
export const themeVar = makeVar('light');
// الاستخدام في المكونات
function Header() {
const isLoggedIn = useReactiveVar(isLoggedInVar);
const currentUser = useReactiveVar(currentUserVar);
const theme = useReactiveVar(themeVar);
const toggleTheme = () => {
themeVar(theme === 'light' ? 'dark' : 'light');
};
return (
<header className={theme}>
{isLoggedIn ? (
<div>مرحبًا، {currentUser.name}</div>
) : (
<button>تسجيل الدخول</button>
)}
<button onClick={toggleTheme}>
تبديل الوضع
</button>
</header>
);
}
import { isLoggedInVar, currentUserVar } from './cache';
// قراءة القيمة الحالية
const isLoggedIn = isLoggedInVar();
// تحديث القيمة
isLoggedInVar(true);
currentUserVar({ id: '1', name: 'جون دو', email: 'john@example.com' });
// في التحويرات
const [login] = useMutation(LOGIN, {
onCompleted: (data) => {
isLoggedInVar(true);
currentUserVar(data.login.user);
}
});
const [logout] = useMutation(LOGOUT, {
onCompleted: () => {
isLoggedInVar(false);
currentUserVar(null);
}
});
معالجة ذاكرة التخزين المؤقت
اقرأ واكتب وعدل ذاكرة التخزين المؤقت Apollo مباشرة لحالات الاستخدام المتقدمة.
import { useApolloClient } from '@apollo/client';
function UserProfile({ userId }) {
const client = useApolloClient();
// القراءة من ذاكرة التخزين المؤقت
const cachedUser = client.readFragment({
id: `User:${userId}`,
fragment: gql`
fragment UserData on User {
id
name
email
}
`
});
// الكتابة إلى ذاكرة التخزين المؤقت
const updateUserInCache = (updates) => {
client.writeFragment({
id: `User:${userId}`,
fragment: gql`
fragment UserData on User {
id
name
email
}
`,
data: {
...cachedUser,
...updates
}
});
};
// الإزالة من ذاكرة التخزين المؤقت (الحذف)
const removeUserFromCache = () => {
client.cache.evict({ id: `User:${userId}` });
client.cache.gc(); // جمع القمامة
};
return (
<div>
{/* واجهة المكون */}
</div>
);
}
معالجة الأخطاء في التحويرات
const [updateUser, { loading, error }] = useMutation(UPDATE_USER, {
onError: (error) => {
if (error.graphQLErrors) {
error.graphQLErrors.forEach(({ message, extensions }) => {
if (extensions?.code === 'UNAUTHENTICATED') {
// إعادة التوجيه إلى تسجيل الدخول
window.location.href = '/login';
} else if (extensions?.code === 'BAD_USER_INPUT') {
// إظهار أخطاء التحقق من الصحة
alert(`خطأ في التحقق: ${message}`);
}
});
}
if (error.networkError) {
alert('خطأ في الشبكة. يرجى المحاولة مرة أخرى.');
}
},
onCompleted: (data) => {
alert('تم تحديث المستخدم بنجاح!');
}
});
- أنشئ تحويرًا لإضافة تعليق إلى منشور باستخدام useMutation
- نفذ تحديث ذاكرة تخزين مؤقت يدوي لإضافة التعليق الجديد إلى قائمة تعليقات المنشور
- أضف واجهة مستخدم متفائلة لإظهار التعليق على الفور قبل تأكيد الخادم
- قم ببناء ميزة بحث باستخدام useLazyQuery الذي ينفذ فقط عندما يقدم المستخدم نموذجًا
- أنشئ متغيرات تفاعلية لتفضيل الوضع الداكن واستخدمها عبر مكونات متعددة