فهم مكونات الخادم مقابل مكونات العميل
قدم Next.js 13 بنية React Server Components (RSC)، مما أحدث تغييراً جوهرياً في طريقة تفكيرنا حول عرض المكونات. يستكشف هذا الدرس الاختلافات بين مكونات الخادم ومكونات العميل، ومتى نستخدم كل منهما، وكيف يعملان معاً.
ما هي مكونات الخادم؟
مكونات الخادم هي مكونات React تعرض حصرياً على الخادم. لا ترسل أبداً JavaScript إلى العميل، مما يجعلها فعالة بشكل لا يصدق لجلب البيانات وتقليل أحجام الحزم.
مفهوم رئيسي: بشكل افتراضي، جميع المكونات في Next.js App Router هي مكونات خادم. هذا تحول أساسي من نموذج React التقليدي حيث يعمل كل شيء على العميل.
فوائد مكونات الخادم
- حزمة JavaScript صفرية: مكونات الخادم لا تضيف أي JavaScript إلى حزمة العميل
- وصول مباشر للخلفية: يمكن الوصول المباشر إلى قواعد البيانات وأنظمة الملفات والموارد الخاصة بالخادم
- أمان محسّن: البيانات الحساسة ومفاتيح API لا تصل أبداً إلى العميل
- أداء أفضل: جلب البيانات يحدث على الخادم، أقرب إلى مصادر البيانات
- تقسيم الكود التلقائي: فقط مكونات العميل يتم تقسيمها وتحميلها عند الطلب
مثال على مكون خادم
// app/posts/page.tsx (مكون خادم بشكل افتراضي)
import { prisma } from '@/lib/prisma';
export default async function PostsPage() {
// وصول مباشر لقاعدة البيانات - لا حاجة لمسار API
const posts = await prisma.post.findMany({
orderBy: { createdAt: 'desc' },
take: 10
});
return (
<div>
<h1>المنشورات الأخيرة</h1>
<ul>
{posts.map(post => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</li>
))}
</ul>
</div>
);
}
ما هي مكونات العميل؟
مكونات العميل هي مكونات React التقليدية التي تعرض على جانب العميل. تمكّن التفاعل و browser APIs وخطافات React مثل useState و useEffect.
متى نستخدم مكونات العميل
تحتاج إلى مكونات العميل عندما يتطلب مكونك:
- التفاعل: معالجات النقر، حقول الإدخال، مستمعي الأحداث
- إدارة الحالة: useState و useReducer و useContext
- التأثيرات: useEffect للتأثيرات الجانبية، الاشتراكات، المؤقتات
- واجهات برمجة المتصفح: localStorage و window و navigator و geolocation
- الخطافات المخصصة: أي خطافات تعتمد على ميزات جانب العميل
- مكونات الفئة: طرق دورة الحياة (على الرغم من ندرتها في React الحديث)
أفضل ممارسة: استخدم مكونات الخادم بشكل افتراضي واختر مكونات العميل فقط عندما تحتاج إلى تفاعل أو ميزات خاصة بالمتصفح. هذا يبقي حجم الحزمة الخاصة بك في الحد الأدنى.
توجيه 'use client'
لتمييز مكون كمكون عميل، أضف توجيه 'use client' في أعلى الملف:
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>العدد: {count}</p>
<button onClick={() => setCount(count + 1)}>
زيادة
</button>
</div>
);
}
مهم: توجيه 'use client' يحتاج فقط إلى إضافة إلى الملفات التي تستخدم ميزات جانب العميل. أي مكونات مستوردة في مكون عميل تصبح تلقائياً مكونات عميل.
أنماط التكوين
فهم كيفية تكوين مكونات الخادم والعميل أمر حاسم لبناء تطبيقات Next.js فعالة.
النمط 1: مكون خادم مع مكونات عميل فرعية
// app/dashboard/page.tsx (مكون خادم)
import { getUser } from '@/lib/auth';
import Counter from '@/components/Counter'; // مكون عميل
export default async function DashboardPage() {
const user = await getUser(); // جلب بيانات جانب الخادم
return (
<div>
<h1>مرحباً، {user.name}!</h1>
{/* محتوى معروض من الخادم */}
<p>بريدك الإلكتروني: {user.email}</p>
{/* مكون عميل للتفاعل */}
<Counter />
</div>
);
}
النمط 2: تمرير مكونات الخادم كخصائص
يمكنك تمرير مكونات الخادم كأطفال أو خصائص لمكونات العميل:
// app/layout.tsx (مكون خادم)
import Sidebar from '@/components/Sidebar'; // مكون عميل
import Feed from '@/components/Feed'; // مكون خادم
export default function Layout({ children }) {
return (
<html>
<body>
{/* تمرير مكون خادم كأطفال */}
<Sidebar>
<Feed /> {/* يبقى هذا مكون خادم */}
</Sidebar>
{children}
</body>
</html>
);
}
// components/Sidebar.tsx (مكون عميل)
'use client';
import { useState } from 'react';
export default function Sidebar({ children }) {
const [collapsed, setCollapsed] = useState(false);
return (
<aside className={collapsed ? 'collapsed' : ''}>
<button onClick={() => setCollapsed(!collapsed)}>
تبديل
</button>
{children} {/* مكون خادم معروض هنا */}
</aside>
);
}
لماذا يعمل هذا: مكون العميل لا يحتاج إلى معرفة ما هو 'children'. يعرضه فقط. يتم تسلسل مكون الخادم وتمريره كخصائص، مع الحفاظ على طبيعته من جانب الخادم.
النمط 3: مشاركة البيانات بين المكونات
// app/products/page.tsx (مكون خادم)
import { getProducts } from '@/lib/db';
import ProductList from '@/components/ProductList'; // مكون عميل
export default async function ProductsPage() {
const products = await getProducts();
// تمرير البيانات المجلوبة من الخادم كخصائص لمكون العميل
return <ProductList initialProducts={products} />;
}
// components/ProductList.tsx (مكون عميل)
'use client';
import { useState } from 'react';
export default function ProductList({ initialProducts }) {
const [products, setProducts] = useState(initialProducts);
const [filter, setFilter] = useState('');
const filteredProducts = products.filter(p =>
p.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<input
type="text"
placeholder="تصفية المنتجات..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<ul>
{filteredProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
تدفق البيانات والتسلسل
عند تمرير البيانات من مكونات الخادم إلى مكونات العميل، يقوم Next.js بتسلسل البيانات. هذا يعني أن البيانات القابلة للتسلسل JSON فقط يمكن تمريرها:
لا يمكن تمرير:
- الدوال
- التواريخ (سيتم تحويلها إلى نصوص)
- نماذج الفئات
- قيم undefined
- Symbols
// ❌ هذا لن يعمل
export default async function Page() {
const handleClick = () => console.log('تم النقر');
return <ClientComponent onClick={handleClick} />;
}
// ✅ هذا يعمل - عرّف المعالج في مكون العميل
// مكون خادم
export default async function Page() {
return <ClientComponent />;
}
// مكون عميل
'use client';
export default function ClientComponent() {
const handleClick = () => console.log('تم النقر');
return <button onClick={handleClick}>انقر</button>;
}
حدود الشبكة
توجيه 'use client' ينشئ حداً بين كود الخادم والعميل. كل ما يتم استيراده في مكون عميل يصبح جزءاً من حزمة العميل:
// components/DataDisplay.tsx
'use client'; // هذا يحدد الحد
import { format } from 'date-fns'; // سيتم تضمينه في حزمة العميل
import { useState } from 'react';
export default function DataDisplay({ data }) {
const [expanded, setExpanded] = useState(false);
return (
<div>
<button onClick={() => setExpanded(!expanded)}>
تبديل
</button>
{expanded && (
<p>{format(new Date(data.date), 'PPP')}</p>
)}
</div>
);
}
نصيحة للتحسين: إذا كنت تحتاج فقط إلى كود جانب العميل في جزء من شجرة المكونات، قم بإنشاء مكونات عميل منفصلة لتلك الأجزاء فقط. هذا يبقي أكبر قدر ممكن من الكود في مكونات الخادم.
الأخطاء الشائعة
1. استيراد كود خاص بالخادم فقط في مكونات العميل
// ❌ هذا سيسبب خطأ
'use client';
import { prisma } from '@/lib/prisma'; // مكتبة خاصة بالخادم فقط
export default function Component() {
// هذا سيفشل - prisma لا يمكن تشغيله في المتصفح
const data = await prisma.user.findMany();
return <div>{data}</div>;
}
2. استخدام 'use client' دون داعٍ
// ❌ غير ضروري - لا توجد ميزات عميل مستخدمة
'use client';
export default function Header({ title }) {
return <h1>{title}</h1>;
}
// ✅ أفضل - اتركه كمكون خادم
export default function Header({ title }) {
return <h1>{title}</h1>;
}
3. محاولة استيراد مكونات خادم في مكونات عميل
// ❌ هذا لن يعمل كما هو متوقع
'use client';
import ServerComponent from './ServerComponent'; // سيصبح مكون عميل
export default function ClientComponent() {
return <ServerComponent />; // لم يعد يعمل على الخادم
}
// ✅ أفضل - مرره كأطفال
// في مكون خادم الأب
<ClientComponent>
<ServerComponent />
</ClientComponent>
شجرة القرار: مكون خادم أم عميل؟
اسأل نفسك هذه الأسئلة:
- هل أحتاج إلى تفاعل؟ (نقرات، إدخالات) ← مكون عميل
- هل أحتاج إلى حالة React أو تأثيرات؟ ← مكون عميل
- هل أحتاج إلى واجهات برمجة المتصفح؟ ← مكون عميل
- هل أنا فقط أعرض البيانات؟ ← مكون خادم
- هل أحتاج إلى جلب البيانات؟ ← مكون خادم (مفضل)
- هل أحتاج إلى الوصول إلى قواعد البيانات مباشرة؟ ← مكون خادم
تمرين عملي
المهمة: أنشئ صفحة منشور مدونة:
- يجلب بيانات المنشور من قاعدة البيانات (مكون خادم)
- يعرض محتوى المنشور (مكون خادم)
- لديه زر "إعجاب" مع عداد (مكون عميل)
- لديه نموذج تعليق (مكون عميل)
- يعرض التعليقات الموجودة (مكون خادم)
المتطلبات:
- استخدم مكونات الخادم لجميع عمليات جلب البيانات
- استخدم مكونات العميل فقط حيث يكون التفاعل مطلوباً
- مرر البيانات من مكونات الخادم إلى العميل بشكل صحيح
- تأكد من التكوين الصحيح للمكونات
التحدي الإضافي: نفذ تحديثات واجهة مستخدم متفائلة لزر الإعجاب والتعليقات دون إعادة جلب الصفحة بأكملها.
الملخص
- مكونات الخادم تعرض على الخادم وترسل صفر JavaScript إلى العميل
- مكونات العميل تمكّن التفاعل وتستخدم توجيه 'use client'
- بشكل افتراضي، جميع المكونات في Next.js App Router هي مكونات خادم
- مكونات الخادم يمكنها الوصول المباشر إلى قواعد البيانات والموارد الخاصة بالخادم
- مكونات العميل مطلوبة للحالة والتأثيرات ومعالجات الأحداث وواجهات برمجة المتصفح
- يمكنك تكوين مكونات الخادم والعميل من خلال تمرير الأطفال أو الخصائص
- البيانات الممررة بين مكونات الخادم والعميل يجب أن تكون قابلة للتسلسل JSON
- استخدم مكونات الخادم بشكل افتراضي واختر مكونات العميل فقط عند الحاجة