frontend

React Compiler مش بيعمل Memoizing للـ Component بتاعك؟ الحل (2026)

١٣ يوليو ٢٠٢٦

React Compiler Not Memoizing Your Component? Fix It (2026)

يقوم مترجم React بتخطي المكونات (أو ما يعرف بـ "bails out on") عندما يحتمل أن ينتهك الكود الخاص بك "قواعد React"، حيث يتركه صراحةً بدون "memoization" بدلاً من المخاطرة بتعطيل تطبيقك. تحقق من شارة "Memo ✨" في أدوات مطوري React (DevTools)، ثم استخدم الـ Playground للعثور على السبب الدقيق.12

ملخص

تم إطلاق النسخة المستقرة من مترجم React Compiler 1.0 في 7 أكتوبر 2025، وقام إصدار Next.js 16 بترقية خيار reactCompiler من كونه علامة تجريبية إلى خيار تكوين مستقر على المستوى الأعلى — لكنه ليس مفعلاً بشكل افتراضي في كل من React أو Next.js.34 لا يظهر المترجم خطأً عندما يتخطى مكوناً ما؛ بل ببساطة لا يقوم بتحسينه، وما لم تكن تتابع شارة "Memo ✨" في أدوات مطوري React، فلن تلاحظ ذلك.1 يستعرض هذا المنشور بدقة كيفية اكتشاف التخطي (bailout)، وما تقوله الوثائق الرسمية حول الأسباب الفعلية لحدوثه، وكيفية عزله باستخدام توجيه "use no memo" و Playground الخاص بمترجم React، وكيفية تكوين المترجم بشكل صحيح في Next.js 16 و Vite 6.

ما ستتعلمه

  • كيفية معرفة ما إذا كان مترجم React قد قام بالفعل بتحسين مكون معين
  • ما هو "تخطي" (bailout) المترجم والأنماط الرسمية التي تتسبب في حدوثه
  • سير العمل الدقيق خطوة بخطوة الذي توصي به وثائق React الرسمية لتصحيح أخطاء التخطي
  • هل لا تزال بحاجة إلى useMemo و useCallback و React.memo بمجرد تفعيل المترجم
  • كيفية الانتقال من حزمة eslint-plugin-React-compiler القديمة إلى الإعداد الحالي
  • كيفية تفعيل المترجم بشكل صحيح في Next.js 16 وفي Vite 6+
  • كيفية استخدام توجيهات "use no memo" و "use memo" للتحكم في عملية الترجمة لكل مكون على حدة
  • هل مترجم React مستقر بما يكفي ليتم استخدامه في بيئة الإنتاج في عام 2026

لماذا لا يقوم مترجم React بعمل memoizing للمكون الخاص بي؟

لأن المكون الخاص بك يحتوي على نمط لا يستطيع المترجم إثبات أمان تحسينه بشكل ثابت، لذا فإنه "يتخطى" (bails out) ويترك هذا المكون تماماً كما كتبته — بدون خطأ، وبدون تحذير في وحدة التحكم (console)، فقط كود غير معالج بتقنية memoization بصمت.2 دليل تصحيح الأخطاء الخاص بـ React صريح في أن تخطي المترجم هو ميزة أمان، وليس خطأً: "عندما يواجه كوداً قد ينتهك هذه القواعد، فإنه يتخطى التحسين بأمان بدلاً من المخاطرة بتغيير سلوك تطبيقك."2 الفئتان العريضتان للكود الذي يتسبب في التخطي، وفقاً لوثائق React، هما المكونات التي تعتمد على memoization من أجل الصحة البرمجية وليس الأداء — على سبيل المثال، التأثيرات (effects) التي تعتمد على كائن أو مصفوفة تحتفظ بنفس المرجع تماماً عبر عمليات الرندر — والمكونات ذات الـ memoization اليدوي غير المكتمل، حيث يفتقد useMemo أو useCallback الحالي إلى تبعية (dependency) يمكن للمترجم رؤيتها ولكنه لا يستطيع حلها بأمان نيابة عنك.25

كيف أعرف ما إذا كان مترجم React قد قام بالفعل بترجمة مكون ما؟

ابحث عن شارة "Memo ✨" بجوار اسم المكون في أدوات مطوري React (DevTools) — إذا كانت مفقودة، فهذا يعني أن المترجم قد تخطى هذا المكون.1 خطوات التحقق الرسمية هي:

  1. قم بتثبيت امتداد المتصفح React Developer Tools وافتح تطبيقك في وضع التطوير (development mode).
  2. افتح علامة التبويب Components وابحث عن إيموجي ✨ بجوار أسماء المكونات.
  3. إذا تمت ترجمة المكون، فسترى شارة "Memo ✨"؛ وإذا كانت مفقودة، فهذا يعني أنه تم تخطي هذا المكون.1

يمكنك تأكيد الشيء نفسه من خلال قراءة مخرجات البناء (build output) مباشرة. مخرجات المكون المترجم تستورد مساعد تخزين مؤقت (cache helper) من وقت تشغيل React الخاص وتغلف قيمة الإرجاع في فحص memoization:

import { c as _c } from "React/compiler-runtime";

export default function MyApp() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("React.memo_cache_sentinel")) {
    t0 = <div>Hello World</div>;
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  return t0;
}

إذا كانت المخرجات المترجمة لمكون معين لا تبدو هكذا — لا يوجد استدعاء لـ _c()، ولا استيراد لـ compiler-runtime — فهذا يعني أن المترجم قد تخطاه.1 للحصول على حلقة تغذية راجعة أسرع أثناء تصحيح أخطاء مكون واحد بنشاط، قم بلصقه في React Compiler Playground الرسمي — فهو يعرض المخرجات المترجمة وأي سبب للتخطي فوراً، دون الحاجة إلى بناء كامل.3

ما الذي يسبب تخطي (bailout) مترجم React؟

تعود معظم حالات التخطي إلى أحد أمرين: انتهاك لقواعد React قام ملحق ESLint بالفعل بتمييزه، أو memoization يدوي لا يستطيع المترجم توسيعه بأمان لأن مصفوفة التبعيات الخاصة به غير مكتملة.25 تلتقط قاعدة التنسيق preserve-manual-memoization الحالة الثانية مباشرةً — سيقوم مترجم React فقط بترجمة مكون إذا كان الـ memoization المستنتج الخاص به يطابق أو يتجاوز أي useMemo أو useCallback أو React.memo كتبته بالفعل، وتمنعه مصفوفة التبعيات غير المكتملة من القيام بذلك:

// التبعية المفقودة تمنع المزيد من تحسين المترجم
function Component({ data, filter }) {
const filtered = useMemo(
() => data.filter(filter),
[data] // 'filter' مفقود
);
return <List items={filtered} />;
}

الحل هو إما إكمال مصفوفة التبعيات، أو حذف الـ memoization اليدوي تماماً وترك المترجم يستنتجه من الصفر — كلاهما صحيح وفقاً للوثائق الرسمية، والثاني عادة ما يكون أبسط بمجرد أن تثق في المترجم.5 الحالة الأولى — الكود الذي يعتمد على memoization من أجل الصحة البرمجية — هي أكثر دقة: فهي تعني أن سلوك تطبيقك يعتمد على قيمة معينة تحتفظ بنفس مرجع الكائن عبر عمليات الرندر (سبب شائع هو مصفوفة تبعيات التأثير)، وبما أن المترجم قد يقوم بعمل memoize بشكل مختلف عما فعله الكود اليدوي، فإن تغيير هذا المرجع يمكن أن يتسبب في تشغيل التأثيرات بشكل مفرط، أو عدم تشغيلها، أو إنشاء حلقات تكرارية.2

كيف يمكنني تصحيح أخطاء التخطي الصامت خطوة بخطوة؟

اتبع سير عمل تصحيح الأخطاء الذي توصي به وثائق React نفسها: اعزل المكون باستخدام "use no memo"، واختبره بدون أي memoization، وقم بإصلاح انتهاك قواعد React الأساسي، ثم تأكد من عودة الشارة.2 بالتفصيل:

  1. قم بتعطيل الترجمة مؤقتاً في المكون المشتبه به باستخدام توجيه "use no memo"، والذي يوضع كأول سطر في جسم الدالة:
function ProblematicComponent() {
  "use no memo"; // تخطي التحويل البرمجي لهذا المكون

  // ... باقي المكون
}

إذا اختفى الخطأ الذي تلاحقه بمجرد تعطيل التحويل البرمجي، فمن المرجح جداً أنه انتهاك لقواعد React كشفه المترجم ولم يتسبب فيه.2

  1. قم بإزالة الحفظ اليدوي (manual memoization) (useMemo، useCallback، React.memo) من نفس المكون للتحقق مما إذا كان تطبيقك لا يزال يسيء التصرف مع عدم وجود أي نوع من الحفظ. إذا حدث ذلك، فلديك انتهاك حقيقي لقواعد React يجب إصلاحه، بشكل مستقل عن المترجم.2
  2. أصلح السبب الجذري أولاً، واختبر بعد كل تغيير، وفقط بعد ذلك قم بإزالة توجيه "use no memo".
  3. تحقق من ظهور شارة "Memo ✨" مرة أخرى في DevTools بمجرد إزالة التوجيه — وهذا يؤكد أن المترجم يقوم بتحسين المكون مرة أخرى.2

إذا كنت تشك في وجود خطأ فعلي في المترجم بدلاً من انتهاك قواعد React في الكود الخاص بك، فإن إرشادات React هي إعادة إنتاجه بشكل مبسط، والتأكد من أنه يحدث فقط عند تمكين التحويل البرمجي، وتقديمه كبلاغ ضد مستودع GitHub الخاص بـ React/React مع تحديد إصدارات React والمترجم بدقة.2

هل ما زلت بحاجة إلى useMemo و useCallback و React.memo مع مترجم React؟

بالنسبة للكود الجديد، لا — فالتليل الخاص بالمترجم، على حد تعبير فريق React، "دقيق بقدر أو أكثر" مما يكتبه معظم المطورين يدوياً، ويمكنه حتى الحفظ في أماكن لا تستطيع الخطافات (hooks) اليدوية الوصول إليها، مثل القيم المحسوبة بعد return شرطي مبكر.3 بالنسبة للكود الحالي، التوصية الرسمية أكثر تحفظاً: اترك استدعاءات useMemo/useCallback/React.memo الحالية في مكانها بدلاً من إزالتها بشكل تلقائي، لأن إزالة الحفظ اليدوي يمكن أن تغير ما يقرر المترجم تحويله برمجياً، وهذا تغيير في السلوك يستحق الاختبار بدلاً من افتراض أنه آمن.3 الحالة الوحيدة التي لا يزال فيها الحفظ اليدوي مفيداً حتى مع تمكين المترجم هي عندما يتم استهلاك قيمة محفوظة كاعتماد لتأثير (effect dependency) وتحتاج إلى تحكم دقيق وصريح في وقت إعادة تشغيل هذا التأثير بالضبط.3

هل يجب أن أنتقل من eslint-plugin-React-compiler إلى eslint-plugin-React-hooks؟

نعم — اعتباراً من إصدار 1.0، يتم شحن قواعد lint المدعومة بالمترجم داخل إعدادات recommended و recommended-latest الخاصة بـ eslint-plugin-React-hooks، ويوصي فريق React صراحةً بإزالة حزمة eslint-plugin-React-compiler المستقلة لصالحها.3 لا يتطلب الـ linter تثبيت المترجم نفسه، لذا لا يوجد خطر في ترقية eslint-plugin-React-hooks حتى قبل تشغيل المترجم:3

npm install --save-dev eslint-plugin-React-hooks@latest
// eslint.config.js (Flat Config)
import reactHooks from 'eslint-plugin-React-hooks';
import { defineConfig } from 'eslint/config';

export default defineConfig([
  reactHooks.configs.flat.recommended,
]);

تشمل القواعد التي تم ذكرها تحديداً في مؤتمر React Conf 2025 قاعدة set-state-in-render (تلتقط استدعاءات setState التي تسبب حلقات رندر لا نهائية)، و set-state-in-effect (تضع علامة على العمل المكلف المخفي داخل التأثيرات)، و refs (تمنع الوصول غير الآمن للـ ref أثناء الرندر) — يتم شحن الثلاثة كجزء من نفس عملية نقل الحزمة.3 لا تزال حزمة eslint-plugin-React-compiler القديمة منشورة على npm، ولكن لا يوجد سبب لإبقائها مثبتة بجانب eslint-plugin-React-hooks@latest بمجرد انتقالك.3

كيف أقوم بإعداد مترجم React في Next.js 16؟

قم بتثبيت babel-plugin-React-compiler واضبط خيار reactCompiler في المستوى الأعلى في next.config.ts — اعتباراً من Next.js 16، أصبح هذا مفتاح تكوين مستقر، وليس علامة تجريبية، على الرغم من أنه لا يزال غير مفعل افتراضياً.4

npm install -D babel-plugin-React-compiler
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  reactCompiler: true,
}

export default nextConfig

وثائق Next.js صريحة بشأن سبب عدم كونه الافتراضي بعد: "أصبح الدعم المدمج لمترجم React مستقراً الآن في Next.js 16 بعد إصدار مترجم React 1.0... تم ترفيع خيار التكوين reactCompiler من experimental إلى مستقر. لم يتم تمكينه افتراضياً بينما نواصل جمع بيانات أداء البناء عبر أنواع التطبيقات المختلفة."4 يخبرك نفس الدليل بتوقع أن تكون أوقات التحويل البرمجي في التطوير وأثناء البناء أعلى بمجرد تمكينه، لأن المترجم الأساسي لا يزال يعمل على Babel.4 تصف وثائق مرجع reactCompiler المنفصلة التأثير بشكل أكثر تفاؤلاً: تشحن Next.js ممر SWC مخصصاً يوجه فقط الملفات التي تحتوي على JSX أو خطافات (hooks) عبر المترجم القائم على Babel بدلاً من كل ملف في مشروعك، وهو ما تصفه الوثائق بأنه يبقي التباطؤ "صغيراً وموضعياً" مقارنة بمترجم Next.js الافتراضي القائم على Rust.6 إذا كنت تريد تحكماً أدق من مجرد علامة "كل شيء أو لا شيء"، فإن Next.js تدعم أيضاً وضع التعليق التوضيحي (annotation mode)، حيث يتم تحويل المكونات المميزة صراحةً بتوجيه 'use memo' فقط:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  reactCompiler: {
    compilationMode: 'annotation',
  },
}

export default nextConfig
// app/page.tsx
export default function Page() {
  'use memo'
  // ...
}

لا يزال "use no memo" يعمل كعكس ذلك — استبعاد مكون واحد خارج التحويل البرمجي حتى عندما تكون العلامة العامة مفعلة.6

كيف أقوم بإعداد مترجم React في Vite؟

إذا كنت تستخدم @vitejs/plugin-React الإصدار 6.0.0 أو أحدث (الإصدار المنشور الحالي هو 6.0.3)، فاستخدم تصدير reactCompilerPreset بجانب @rolldown/plugin-babel — تمت إزالة خيار babel المضمن القديم للمكون الإضافي في ذلك الإصدار:1

npm install -D @rolldown/plugin-babel
// vite.config.js
import { defineConfig } from 'vite';
import React, { reactCompilerPreset } from '@vitejs/plugin-React';
import babel from '@rolldown/plugin-babel';

export default defineConfig({
  plugins: [
    React(),
    babel({
      presets: [reactCompilerPreset()]
    }),
  ],
});

إذا كنت لا تزال تستخدم إصداراً أقدم من @vitejs/plugin-React، فسيتم تكوين المترجم من خلال خيار babel المضمن في المكون الإضافي والذي أصبح الآن مهجوراً:

// vite.config.js (pre-6.0.0 @vitejs/plugin-React)
import { defineConfig } from 'vite';
import React from '@vitejs/plugin-React';

export default defineConfig({
  plugins: [
    React({
      babel: {
        plugins: ['babel-plugin-React-compiler'],
      },
    }),
  ],
});

إذا كنت تستخدم React Router بدلاً من Vite + React العادي، فإن وثائق React نفسها توجهك إلى vite-plugin-babel مباشرةً بدلاً من الإعداد المسبق لـ @vitejs/plugin-React.1 في كلتا الحالتين، وأياً كانت أداة البناء التي تستخدمها، يجب أن يعمل ملحق المترجم (compiler plugin) أولاً في خط أنابيب ملحقات Babel — فهو يحتاج إلى المصدر الأصلي غير المحول لتحليل تدفق البيانات بشكل صحيح.1

هل مترجم React مستقر بما يكفي للإنتاج في عام 2026؟

نعم، وفقاً لتقرير فريق React نفسه — تم إطلاق الإصدار 1.0 في 7 أكتوبر 2025 كإصدار "تم اختباره في المعارك على تطبيقات كبرى في Meta" و "جاهز تماماً للإنتاج"، حيث أبلغ متجر Meta Quest عن تحميلات أولية أسرع بنسبة تصل إلى 12% وتنقلات بين الصفحات أسرع، وبعض التفاعلات أسرع بأكثر من 2.5 مرة، مع استهلاك محايد للذاكرة.3 تدعم الإعدادات الافتراضية للمشاريع الجديدة هذا التوجه: يتم شحن Expo SDK 54 والإصدارات الأحدث مع تفعيل المترجم تلقائياً، كما يقدم كل من create-vite و create-next-app الآن قوالب مفعلة للمترجم للمشاريع الجديدة.3 ومع ذلك، فإن "مستقر" و "مفعل افتراضياً" هما أمران مختلفان — حتى وقت كتابة هذا التقرير، لا تقوم أدوات React العادية ولا Next.js 16 بتشغيل المترجم تلقائياً للمشاريع الحالية، وتحديداً لأن مطوري الإطارات البرمجية لا يزالون يجمعون بيانات أداء البناء في العالم الحقيقي قبل اتخاذ هذه الخطوة.4 إن أسلم مسار لاعتماد المترجم في قاعدة كود موجودة، وفقاً لإرشادات الترقية الخاصة بـ React، هو تثبيت المترجم على إصدار محدد (--save-exact، وليس نطاق semver) بدلاً من تركه عائماً على ^1.0.0، لأن الإصدارات المستقبلية قد تغير مدى دقة حفظ القيم في الذاكرة (memoization) — وإذا كانت تأثيراتك (effects) تعتمد على قيم محددة تحافظ على مراجع مستقرة، فإن هذا النوع من التغيير يمكن أن يغير السلوك حتى لو لم تتغير واجهة API.3

الخلاصة

انسحاب مترجم React ليس تقريراً عن خطأ — بل هو رفض المترجم لتحسين كود لا يمكنه إثبات سلامته، وهو يفشل بصمت حسب التصميم. تحقق من شارة "Memo ✨" في أدوات مطوري React أولاً، ثم أعد إنتاج المكون المشكوك فيه في الـ Playground ثانياً، وفقط بعد ذلك استخدم "use no memo" لتحديد ما إذا كنت تواجه انتهاكاً حقيقياً لقواعد React في الكود الخاص بك. بمجرد الانتقال إلى eslint-plugin-React-hooks@latest وإعداد المترجم بشكل صحيح لأداة البناء الخاصة بك — علامة reactCompiler المستقرة في Next.js 16، أو reactCompilerPreset في @vitejs/plugin-React 6+ — ستظهر معظم حالات الانسحاب المستقبلية كأخطاء ESLint قبل أن تصل إلى مرحلة التصحيح. لأنماط React 19 ذات الصلة، راجع أدلتنا حول بناء النماذج باستخدام React Hook Form و Zod، و التحديثات المتفائلة والتراجع مع TanStack Query، و Server Actions وواجهة المستخدم المتفائلة في Next.js 16.

Footnotes

  1. React، "التثبيت – مترجم React،" https://React.dev/learn/React-compiler/installation — تعليمات إعداد Babel/Vite/Next.js/React Router، التحقق من شارة "Memo ✨"، مثال على مخرجات البناء المجمعة، خيار التعطيل "use no memo". تم الاسترجاع في 13-07-2026. 2 3 4 5 6 7 8 9

  2. React، "تصحيح الأخطاء واستكشاف المشكلات وإصلاحها – مترجم React،" https://React.dev/learn/React-compiler/debugging — التمييز بين أخطاء المترجم ومشكلات وقت التشغيل، الأنماط الشائعة المسببة للأعطال، سير عمل تصحيح الأخطاء خطوة بخطوة، عملية الإبلاغ عن الأخطاء. تم الاسترجاع في 13-07-2026. 2 3 4 5 6 7 8 9 10 11 12 13

  3. React، "مترجم React الإصدار 1.0،" https://React.dev/blog/2025/10/07/React-compiler-1 — تاريخ الإصدار المستقر، أرقام أداء Meta Quest Store، إرشادات الهجرة لـ eslint-plugin-React-hooks، إرشادات useMemo/useCallback/React.memo، مرجع Playground، الإعدادات الافتراضية للتطبيقات الجديدة، إرشادات الترقية والإصدارات. تم الاسترجاع في 13-07-2026. 2 3 4 5 6 7 8 9 10 11 12 13 14 15

  4. Next.js، "كيفية الترقية إلى الإصدار 16،" https://nextjs.org/docs/app/guides/upgrading/version-16 (آخر تحديث 13-05-2026، إصدار التوثيق 16.2.10) — قسم دعم مترجم React: الترقية من الوضع التجريبي إلى المستقر، مبررات عدم التفعيل افتراضيًا، خطوات التثبيت. تم الاسترجاع في 13-07-2026. 2 3 4 5 6

  5. React، "preserve-manual-memoization – eslint-plugin-React-hooks،" https://React.dev/reference/eslint-plugin-React-hooks/lints/preserve-manual-memoization — تفاصيل القاعدة وأمثلة على مصفوفة التبعيات الصالحة وغير الصالحة. تم الاسترجاع في 13-07-2026. 2 3 4

  • Next.js، "reactCompiler،" https://nextjs.org/docs/app/API-reference/config/next-config-js/reactCompiler (آخر تحديث 2026-02-11، إصدار التوثيق 16.2.9) — استهداف الملفات المحسن بواسطة SWC، وضع التعليقات التوضيحية، توجيهات 'use memo'/"use no memo". تم الجلب في 2026-07-13. 2 3

  • الأسئلة الشائعة

    يحتوي المكون الخاص بك على نمط — عادةً ما يكون انتهاكاً لقواعد React (Rules of React) أو مصفوفة تبعيات يدوية غير مكتملة — لا يستطيع المترجم إثبات أنه آمن للتحسين، لذا ينسحب بصمت بدلاً من المخاطرة بتغيير سلوك تطبيقك. 2 5