تخطَّ إلى المحتوى
NerdLevelTech
تطوير الواجهة الأمامية

أسئلة وأجوبة مقابلات React (٢٠٢٦)

تحضير لمقابلات React 19: Server Components، وActions، وuse()، وref-as-prop، وReact Compiler، والفخاخ التي يبحث عنها المحاورون.

Nerd Level Techنُشر في تم التحديث ١٣ دقيقة للقراءةEnglish
صورة غلاف: أسئلة وأجوبة مقابلات React (٢٠٢٦)

أُعيد إنشاؤه: ٢٧ مارس ٢٠٢٦ · آخر تحقق: ٣ نوفمبر ٢٠٢٦.

ملخص

تركز مقابلات React في عام ٢٠٢٦ على Server Components الخاصة بـ React 19 (المستقرة منذ ديسمبر ٢٠٢٤)، و Actions، و hooks النماذج الجديدة (useActionState, useOptimistic, useFormStatus)، و API الخاص بـ use()، و ref-as-a-prop، و React Compiler (الإصدار المستقر v1.0 منذ أكتوبر ٢٠٢٥). كن مستعداً لشرح متى يتم الرندرة (render) على السيرفر مقابل الكلاينت، وكيف تتعامل Actions مع حالات الانتظار (pending) والأخطاء تلقائياً، ولماذا يغير الـ memoization التلقائي طريقة هيكلة المكونات. من المتوقع الإلمام بـ Next.js App Router لأنه هدف النشر الأكثر شيوعاً لـ React 19.

لقد تطور React بشكل كبير منذ أيامه الأولى. ما كان يعتبر نمطاً متطوراً قبل عامين قد يُعتبر اليوم قديماً (legacy). إذا كنت تستعد لمقابلة React في عام ٢٠٢٦، فأنت بحاجة لفهم ليس فقط الأساسيات — التي لم تتغير — ولكن أيضاً النموذج الحديث: React 19 (المستقر منذ ٥ ديسمبر ٢٠٢٤)، و Server Components، ونموذج Actions، و React Compiler v1.0 (المستقر منذ ٧ أكتوبر ٢٠٢٥). أضاف إصدار React 19.2 (أكتوبر ٢٠٢٥) مكون <Activity />، و useEffectEvent، و Partial Pre-rendering — وسيسأل المحاورون في الشركات التي تستخدم أحدث إصدار فرعي عن هذه الميزات أيضاً.

يغطي هذا الدليل الأسئلة التي من المرجح أن تواجهها، منظمة حسب المفهوم مع إجابات عملية وأمثلة برمجية. سواء كنت تتقدم لمقابلة لأول دور وظيفي لك في React أو تنتقل إلى منصب سينيور، فإن هذه المواضيع تشكل أساس تطوير React الحديث.

فهم التحول الجذري في نموذج React 19

س: ما هو الفرق الجوهري بين Server Components و Client Components؟

ج: أصبحت Server Components مستقرة في React 19 (ديسمبر ٢٠٢٤) بعد سنوات في قناة Canary. هي تُنفذ فقط على السيرفر، ولا ترسل JavaScript أبداً إلى الكلاينت، ويمكنها الوصول إلى قواعد البيانات، و APIs، والأسرار (secrets) مباشرة. أما Client Components فتُنفذ في المتصفح وهي المكان الوحيد الذي تكون فيه hooks مثل useState و useEffect صالحة.

في إطار عمل مثل Next.js App Router، تكون Server Components هي الافتراضية. تقوم بإضافة توجيه 'use client' في أعلى الملف لضم هذا الموديول (وكل ما يستورده) إلى مخطط Client Component:

jsx
// app/posts/PostList.tsx — Server Component (default)
export default async function PostList() {
  const posts = await db.posts.findAll();
  return (
    <ul>
      {posts.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  );
}
jsx
// app/Counter.tsx — Client Component (explicitly marked)
'use client';
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

لماذا هذا مهم: تقلل Server Components من حجم JavaScript المرسل إلى المتصفح، وتلغي طبقة واحدة من شلال جلب البيانات (data-fetching waterfall)، وتسمح ببقاء الأسرار على السيرفر. هي ليست بديلاً لـ Client Components — بل مكملة لها.

س: كيف تؤثر Server Components على جلب البيانات؟

ج: في Server Components، يمكنك استخدام async/await مباشرة دون الانتقال إلى API route. تقوم بجلب البيانات من جهة السيرفر قبل الرندرة:

jsx
// No API route needed
export default async function UserProfile({ userId }) {
  const user = await fetch(`https://api.example.com/users/${userId}`).then(r => r.json());

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
}

إذا كان المكون يحتاج إلى تفاعلية (مثل نموذج)، فإنك تستخرج الجزء التفاعلي في Client Component وتمرر البيانات كـ props.

React Actions والتعامل مع النماذج

س: ما هي React Actions ولماذا هي مهمة؟ (وكيف تختلف عن Server Actions؟)

ج: هذا فخ شائع في المقابلات، لذا كن دقيقاً. Actions في React 19 هي دوال async يتم تمريرها إلى async transitions — حيث يتتبع React حالة الانتظار (pending state)، ويعالج الأخطاء عبر Error Boundaries، ويدعم التحديثات التفاؤلية (optimistic updates). هي تعمل أينما تم تعريفها؛ لا يوجد شيء في Action يتطلب وجود سيرفر. أما Server Actions فهي Actions مميزة بتوجيه 'use server'، مما يجعلها قابلة للاستدعاء من Client Components ولكن يتم تنفيذها على السيرفر. آلية عمل Action هي نفسها؛ التوجيه هو ما يجعلها مقتصرة على السيرفر.

كما منح React 19 دعماً من الدرجة الأولى لـ <form>: قم بتمرير دالة إلى prop الـ action (أو formAction)، وسيقوم React باستدعائها مع بيانات النموذج داخل transition.

jsx
'use client';
import { createPost } from './actions';

export default function CreatePostForm() {
  return (
    <form action={createPost}>
      <input type="text" name="title" required />
      <textarea name="content" required />
      <button type="submit">Create Post</button>
    </form>
  );
}

وفي ملف actions.ts — هذه Server Action:

typescript
'use server';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  const title = formData.get('title');
  const content = formData.get('content');

  await db.posts.create({ title, content });
  revalidatePath('/posts');
}

س: كيف تتناسب useActionState و useOptimistic و useFormStatus في هذا السياق؟

ج: هذه هي الـ hooks الثلاثة الجديدة التي تحيط بـ Actions. معرفة وظيفة كل واحدة منها هي أكثر سؤال متابعة شائع في React 19.

  • useActionState(action, initialState) — تغلف Action وتعيد [state, dispatch, isPending]. استخدمها لتتبع نتيجة آخر عملية إرسال وحالة التنفيذ الحالية.
  • useOptimistic(currentState) — تتيح لك عرض تحديث تفاؤلي (optimistic update) فوراً بينما لا يزال الـ Action قيد التنفيذ؛ وReact تعود للحالة السابقة تلقائياً إذا فشل الـ Action.
  • useFormStatus() — يتم استدعاؤها من عنصر ابن لـ <form> لقراءة حالة الانتظار (pending status) للنموذج الأب (تُستخدم لتعطيل زر الإرسال أثناء الإرسال، على سبيل المثال).
jsx
'use client';
import { useActionState, useOptimistic } from 'react';
import { useFormStatus } from 'react-dom';
import { createPost } from './actions';

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>;
}

export default function PostForm({ posts }) {
  const [optimisticPosts, addOptimistic] = useOptimistic(
    posts,
    (state, newPost) => [...state, newPost],
  );
  const [error, submit] = useActionState(async (_prev, formData) => {
    addOptimistic({ title: formData.get('title'), pending: true });
    return await createPost(formData);
  }, null);

  return (
    <form action={submit}>
      <input name="title" required />
      <SubmitButton />
      {error && <p role="alert">{error}</p>}
      <ul>{optimisticPosts.map(p => <li key={p.title}>{p.title}</li>)}</ul>
    </form>
  );
}

ملاحظة: يتم استيراد useFormStatus من react-dom، وليس من react. انتبه لهذه النقطة وستتميز في إجابتك.

الـ API الخاصة بـ use() والعمليات غير المتزامنة (Async Operations)

س: متى تستخدم الـ API الخاصة بـ use()؟

ج: use() هي API جديدة (ليست hook من الناحية التقنية — حيث يمكن استدعاؤها داخل الحلقات التكرارية والشروط، على عكس useContext الملتزمة بقواعد الـ hooks). تقوم بقراءة القيمة من مورد: إما Promise أو Context. عند استدعائها مع Promise، يتوقف المكون (suspends) حتى يتم حل الـ Promise، ويقوم أقرب حدود <Suspense> بعرض حالة الاحتياط (fallback).

jsx
'use client';
import { use } from 'react';

export default function PostContent({ postPromise }) {
  const post = use(postPromise); // suspends until resolved

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

يبدأ الـ Server Component عملية الجلب (بدون استخدام await) ويمرر الـ Promise غير المحلول إلى Client Component:

jsx
import { Suspense } from 'react';
import { PostContent } from './PostContent';

export default function PostPage({ slug }) {
  // No await — kick off the fetch, let the Client Component suspend on it.
  const postPromise = fetch(`https://api.example.com/posts/${slug}`).then(r => r.json());

  return (
    <Suspense fallback={<p>Loading...</p>}>
      <PostContent postPromise={postPromise} />
    </Suspense>
  );
}

هذا النمط يتيح لك تدفق البيانات (stream data) عبر الحدود بين السيرفر والكلينت دون حظر عملية الرندر في السيرفر.

تعمل use() أيضاً مع الـ Context — وعلى عكس useContext، يمكن استدعاؤها بشكل شرطي (بعد عمليات الـ early returns، أو داخل فروع if)، وهذا أحد الأسباب التي تجعلها غير مصنفة كـ hook.

مترجم React (Compiler) والـ Memoization التلقائية

س: ماذا يفعل مترجم React، وهل هو مستقر؟

ج: تم إطلاق مترجم React الإصدار 1.0 كنسخة مستقرة في ٧ أكتوبر ٢٠٢٥، بعد حوالي ١٨ شهراً من المراحل التجريبية والـ beta والـ RC. هو عبارة عن Babel plugin يقوم بعمل memoization تلقائياً للمكونات والقيم والـ callbacks أثناء وقت البناء (build time)، لذا لم تعد بحاجة لاستخدام useMemo أو useCallback أو React.memo يدوياً من أجل التحسينات التقليدية.

هو اختياري للتطبيقات الموجودة. بالنسبة للمشاريع الجديدة، يقوم Expo SDK 54+ بتفعيله افتراضياً، بينما يتيح لك Vite (create-vite) و Next.js (create-next-app) اختيار قالب يدعم المترجم أثناء الإعداد بدلاً من تفعيله افتراضياً. هو متوافق مع React 17 وما فوق — لست بحاجة بالضرورة إلى React 19، رغم أن دمجه مع React 19 هو الحالة الشائعة.

jsx
// Before: manual optimization
function ExpensiveComponent({ data, onUpdate }) {
  const cachedValue = useMemo(() => processData(data), [data]);
  const cachedCallback = useCallback(() => onUpdate(cachedValue), [cachedValue, onUpdate]);

  return <Child value={cachedValue} onChange={cachedCallback} />;
}

// With React Compiler: no optimization code needed
function ExpensiveComponent({ data, onUpdate }) {
  const cachedValue = processData(data); // Compiler auto-memoizes
  const cachedCallback = () => onUpdate(cachedValue); // Auto-memoized callback

  return <Child value={cachedValue} onChange={cachedCallback} />;
}

أسئلة متابعة متوقعة في المقابلات: "هل لا تزال بحاجة إلى useMemo؟" (بشكل عام لا داخل الكود المترجم، ولكن نعم لعزل العمليات الحسابية المكلفة جداً أو عندما يتوقف المترجم عن العمل بسبب أنماط ديناميكية.) "كيف تتبناه تدريجياً؟" (لكل مجلد أو لكل ملف عبر إعدادات المترجم — راجع دليل التبني التدريجي الرسمي.)

الـ ref كـ Prop، و forwardRef

س: كيف تعمل الـ refs في React 19؟

ج: في React 19، تقبل المكونات الوظيفية (function components) الـ ref كـ prop عادي. لم تعد هناك حاجة لـ forwardRef للمكونات الجديدة وسيتم إيقاف دعمها في إصدار رئيسي مستقبلي:

jsx
// React 19: ref is just a prop
function MyInput({ placeholder, ref }) {
  return <input placeholder={placeholder} ref={ref} />;
}

// Usage stays the same
<MyInput ref={inputRef} />

يجب أن تظل على دراية بـ forwardRef لأن كل كود برمجي أقدم من أواخر ٢٠٢٤ يستخدمها. أضافت React 19 أيضاً دعماً لـ دوال تنظيف الـ ref (ref cleanup functions) — إذا أعادت دالة الـ ref callback دالة أخرى، تقوم React باستدعائها عند إلغاء التثبيت (unmount)، مما يحاكي دلالات التنظيف في useEffect.

أنماط Next.js App Router

س: كيف تقوم بهيكلة ميزة full-stack في Next.js App Router؟

ج: النمط هو: Server Component ← Client Component للتفاعل ← Action للتعديلات (mutations) ← إعادة التحقق من التخزين المؤقت (Cache revalidation).

jsx
// app/posts/page.tsx - Server Component
import { PostList } from './components/PostList';

export default async function PostsPage() {
  const posts = await db.posts.findAll();

  return (
    <div>
      <h1>Posts</h1>
      <PostList initialPosts={posts} />
    </div>
  );
}

// components/PostList.tsx - Client Component
'use client';
import { useState } from 'react';
import { deletePost } from '../actions';

export function PostList({ initialPosts }) {
  const [posts, setPosts] = useState(initialPosts);

  async function handleDelete(id) {
    await deletePost(id);
    setPosts(posts.filter(p => p.id !== id));
  }

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          {post.title}
          <button onClick={() => handleDelete(post.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

// actions.ts
'use server';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';

export async function deletePost(id: string) {
  await db.posts.delete(id);
  revalidatePath('/posts');
}

الأنماط القديمة (التي لا تزال تُسأل في المقابلات)

س: ما هي مكونات الفئات (class components) ومتى تظل ذات صلة؟

ج: مكونات الفئات هي الطريقة التي كانت متبعة قبل ظهور الـ hooks لإدارة الحالة (state) والتأثيرات الجانبية (side effects). نادراً ما تُستخدم في الأكواد الجديدة ولكنها تظهر في قواعد الأكواد القديمة (legacy codebases):

jsx
class Counter extends React.Component {
  state = { count: 0 };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

المكونات الوظيفية (Functional components) مع الـ hooks هي المعيار الحديث. إذا واجهت مكونات الفئات في مقابلة عمل، أظهر أنك تفهمها ولكن ركز على النهج القائم على الـ hooks.

س: ما الفرق بين useEffect وجلب البيانات من جهة الخادم (server-side data fetching)؟

ج: يتم تشغيل useEffect في المتصفح بعد عملية الرندرة (rendering). إذا كان بإمكانك جلب البيانات من جهة الخادم (في Server Component أو Action)، فيجب عليك فعل ذلك—فهو أسرع ولا يتسبب في حدوث "شلال تحميل" (loading waterfall):

jsx
// Slower: client-side fetch with waterfall
'use client';
import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(setUser);
  }, [userId]);

  return user ? <p>{user.name}</p> : <p>Loading...</p>;
}

// Faster: server-side fetch
export default async function UserProfile({ userId }) {
  const user = await fetch(`https://api.example.com/users/${userId}`).then(r => r.json());
  return <p>{user.name}</p>;
}

إضافات React 19.2 الجديرة بالمعرفة

تم إطلاق React 19.2 في ١ أكتوبر ٢٠٢٥. وقد اعتمدت معظم الفرق التي تستخدم أحدث إصدار فرعي واحدة على الأقل من هذه الميزات.

  • <Activity /> — يقوم بعمل رندرة مسبقة أو يحافظ على حالة واجهة المستخدم لأجزاء التطبيق التي من المرجح أن ينتقل إليها المستخدم (أو يبتعد عنها)، دون إلغاء التركيب (unmounting). وهذا يحل محل الكثير من أنماط "إبقاء هذا المكون مركباً ولكن مخفياً" اليدوية.
  • useEffectEvent — يفصل المنطق الخاص بالأحداث (event-style logic) عن الـ Effect التفاعلي. الدالة المرتجعة ترى دائماً أحدث الـ props/state دون إعادة تشغيل الـ Effect عند تغيرها. وهذا يحل مشكلة "الإغلاق القديم (stale closure) داخل useEffect" بشكل نظيف.
  • الرندرة المسبقة الجزئية (Partial Pre-rendering) — رندرة الهيكل الثابت مسبقاً، وبث الأجزاء الديناميكية لاحقاً. وهذا مهم بشكل خاص إذا ذكر المحاور Next.js PPR.
  • تجميع Suspense SSR (Suspense SSR Batching) — يتم الكشف عن حدود Suspense التي تمت رندرتها على الخادم في مجموعات، مما يجعل عملية التحميل تبدو أكثر سلاسة.
  • cacheSignal — بالنسبة لـ Server Components، هو عبارة عن AbortSignal يتم إطلاقه عندما لا تعود هناك حاجة لمورد مخزن مؤقتاً.

مواضيع المقابلات الشائعة

نظرة عامة على React Hooks (React 19.2):

  • useState: إدارة الحالة (state management)
  • useEffect: التأثيرات الجانبية والتنظيف (cleanup)
  • useEffectEvent (١٩٫٢): معالجات أحداث غير تفاعلية داخل الـ Effects
  • useContext: قراءة السياق (ملاحظة: use(Context) يعمل بشكل شرطي)
  • useReducer: منطق الحالة المعقد
  • useCallback / useMemo: التخزين المؤقت اليدوي (غالباً ما يكون غير ضروري مع React Compiler)
  • useRef: الوصول إلى عناصر DOM أو التخزين القابل للتغيير
  • useTransition: تحديد تحديثات الحالة على أنها غير عاجلة
  • useDeferredValue: تأجيل رندرة قيمة مكلفة
  • useActionState (١٩): حالة الانتظار/النتيجة للـ Actions
  • useOptimistic (١٩): واجهة مستخدم تفاؤلية أثناء الانتقالات غير المتزامنة
  • useFormStatus (١٩, من react-dom): قراءة حالة الانتظار للنموذج (form) الأب
  • use() (١٩): قراءة Promise أو Context، ويمكن استدعاؤه بشكل شرطي

تحسين الأداء (Performance Optimization):

  • اعتماد React Compiler بدلاً من كتابة useMemo/useCallback يدوياً
  • تقسيم الكود (Code splitting) باستخدام الاستيرادات الديناميكية و React.lazy
  • تحسين الصور (AVIF, WebP) ومكون <Image> الخاص بإطار العمل
  • تحليل الحزمة (Bundle analysis) باستخدام rollup-plugin-visualizer لـ Vite، و @next/bundle-analyzer لـ Next.js
  • التحميل المتأخر (Lazy loading) باستخدام حدود Suspense
  • استخدام React DevTools Profiler لتحديد عمليات الرندرة المكلفة

أفضل الممارسات في الاختبار (Testing Best Practices):

  • اختبارات الوحدة (Unit tests) باستخدام Vitest أو Jest
  • اختبار المكونات باستخدام React Testing Library
  • اختبارات التكامل (Integration tests) للميزات الكاملة
  • اختبارات الطرفين (E2E tests) باستخدام Playwright أو Cypress

النقاط الرئيسية

إن React في عام ٢٠٢٦ يتمحور بشكل أساسي حول الرندرة التي تعتمد على السيرفر أولاً مع تفاعلية تدريجية (server-first rendering with progressive interactivity). تتوقع منك المقابلات الشخصية أن تشرح نموذج React 19 — حيث أصبحت Server Components مستقرة، وActions هي البديل الجديد لعمليات الـ mutation، وuse() تقرأ الـ Promises والـ Context، والـ refs أصبحت مجرد props عادية، بينما يتولى React Compiler (الإصدار ١٫٠، أكتوبر ٢٠٢٥) عملية الـ memoization نيابة عنك. كما أضاف React 19.2 (أكتوبر ٢٠٢٥) كل من <Activity />، وuseEffectEvent، والـ Partial Pre-rendering — ومعرفتك بهذه الميزات تعطي إشارة بأنك مطلع على أحدث التطورات.

كن دقيقاً في توضيح الحد الفاصل بين Actions (وهي الـ transition primitive في React 19) وبين Server Actions (وهي Actions مضاف إليها 'use server'). كن صادقاً بشأن ما يمكن وما لا يمكن لـ Server Components القيام به — فهي ليست بديلاً كاملاً لـ Client Components، والقيمة الحقيقية تأتي من استخدام كل منهما في المكان المناسب.

أتقن هذه المفاهيم الأساسية، وافهم Next.js App Router، وكن مستعداً لمناقشة كل من الأنماط الحديثة والأكواد القديمة (legacy code). يريد المحاورون أن يروا أنك تفهم لماذا تم اتخاذ خيارات معينة، وليس فقط كيف تكتب الكود.

المصادر

ابقَ على مسار النيرد

رسالة واحدة كلّ أسبوع: عدد الخميس يضمّ شرحًا لمنظومة واحدة، وثلاث إشارات، وتمرينًا واحدًا، ووظيفة واحدة، وخلاصة بالعربية.

بدون إزعاج. إلغاء الاشتراك في أي وقت.