React Compiler مع Next.js: دليل التحسين التلقائي الشامل

AI Bot
بواسطة AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

المتطلبات الأساسية

قبل البدء في هذا الدرس، تأكد من توفر:

  • Node.js 20+ مثبّت على جهازك
  • مشروع Next.js 15+ (يُفضّل App Router)
  • فهم أساسي لـ React hooks وآلية الحفظ (useMemo، useCallback، React.memo)
  • محرر أكواد مثل VS Code

ما هو React Compiler؟

React Compiler (المعروف سابقاً بـ React Forget) هو أداة تعمل وقت البناء لتحسين مكونات React تلقائياً عبر إدراج الحفظ (memoization) حيث يلزم. بدلاً من تغليف القيم يدوياً في useMemo والدوال في useCallback والمكونات في React.memo، يقوم المترجم بتحليل الكود وتنفيذ ذلك نيابة عنك.

هذا يعني:

  • لا مزيد من الحفظ اليدوي — المترجم يتكفل بذلك
  • حجم حزمة أصغر — يتم التخلص من إعادة التصيير غير الضرورية وقت البناء
  • أداء أفضل افتراضياً — كل مكون محسّن بدون جهد من المطور
  • كود أنظف — إزالة أغلفة useMemo/useCallback الزائدة

يفهم React Compiler قواعد React ويحوّل الكود بأمان مع الحفاظ على الصحة.

ما الذي ستبنيه

في هذا الدرس، ستقوم بـ:

  1. إعداد React Compiler في مشروع Next.js 15
  2. تكوين إضافة Babel وتكامل ESLint
  3. فهم ما يحسّنه المترجم وكيف
  4. تصحيح المخرجات المترجمة للتحقق من التحسينات
  5. قياس تحسينات الأداء الفعلية
  6. التعامل مع الحالات الخاصة وسيناريوهات إلغاء الاشتراك

الخطوة 1: إنشاء مشروع Next.js

إذا كان لديك مشروع Next.js 15+ بالفعل، انتقل إلى الخطوة 2. وإلا، أنشئ مشروعاً جديداً:

npx create-next-app@latest react-compiler-demo --typescript --tailwind --app --src-dir
cd react-compiler-demo

تحقق من إصدار React:

npm ls react

يجب أن ترى react@19.x.x. يتطلب React Compiler الإصدار 19 أو أحدث.

الخطوة 2: تثبيت React Compiler

ثبّت إضافة Babel وإضافة ESLint للمترجم:

npm install -D babel-plugin-react-compiler eslint-plugin-react-compiler

هاتان الحزمتان هما كل ما تحتاجه:

  • babel-plugin-react-compiler — المترجم الأساسي الذي يحوّل الكود وقت البناء
  • eslint-plugin-react-compiler — يكشف انتهاكات قواعد React التي تمنع الترجمة الآمنة

الخطوة 3: تكوين Next.js

يدعم Next.js 15 React Compiler بشكل مدمج. فعّله في next.config.ts:

import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  experimental: {
    reactCompiler: true,
  },
};
 
export default nextConfig;

هذا السطر الواحد كافٍ لتفعيل المترجم لمشروعك بالكامل. يتعامل Next.js مع تكامل إضافة Babel تلقائياً.

التكوين المتقدم

للمزيد من التحكم، يمكنك تمرير خيارات:

const nextConfig: NextConfig = {
  experimental: {
    reactCompiler: {
      compilationMode: "annotation", // ترجمة المكونات المشتركة فقط
      panicThreshold: "CRITICAL_ERRORS", // فشل البناء عند الأخطاء الحرجة
    },
  },
};

خيارات compilationMode:

الوضعالوصف
"infer"الافتراضي. يترجم جميع المكونات والخطافات تلقائياً
"annotation"يترجم فقط المكونات/الخطافات التي تحتوي على توجيه "use memo"
"all"يترجم كل شيء، حتى لو انتهك قواعد React (استخدم بحذر)

لمعظم المشاريع، الوضع الافتراضي "infer" هو الخيار الصحيح.

الخطوة 4: إعداد تكامل ESLint

أضف إضافة ESLint لاكتشاف انتهاكات قواعد React مبكراً. حدّث .eslintrc.json:

{
  "plugins": ["react-compiler"],
  "rules": {
    "react-compiler/react-compiler": "error"
  }
}

أو إذا كنت تستخدم تكوين ESLint المسطح (eslint.config.mjs):

import reactCompiler from "eslint-plugin-react-compiler";
 
export default [
  {
    plugins: {
      "react-compiler": reactCompiler,
    },
    rules: {
      "react-compiler/react-compiler": "error",
    },
  },
];

شغّل ESLint للتحقق من قاعدة الكود:

npx next lint

ستظهر أي انتهاكات كأخطاء، تخبرك بالضبط ما يجب إصلاحه قبل أن يتمكن المترجم من تحسين الكود بأمان.

الخطوة 5: فهم ما يتم تحسينه

لنلقِ نظرة على مثال عملي. تأمل هذا المكون قبل React Compiler:

// components/ProductList.tsx — قبل التحسين
"use client";
 
import { useState } from "react";
 
interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}
 
function ProductCard({ product }: { product: Product }) {
  console.log(`Rendering ProductCard: ${product.name}`);
  return (
    <div className="border rounded-lg p-4">
      <h3 className="font-bold">{product.name}</h3>
      <p className="text-gray-600">${product.price}</p>
    </div>
  );
}
 
function CategoryFilter({
  categories,
  selected,
  onSelect,
}: {
  categories: string[];
  selected: string;
  onSelect: (cat: string) => void;
}) {
  console.log("Rendering CategoryFilter");
  return (
    <div className="flex gap-2 mb-4">
      {categories.map((cat) => (
        <button
          key={cat}
          onClick={() => onSelect(cat)}
          className={selected === cat ? "bg-blue-500 text-white px-3 py-1 rounded" : "bg-gray-200 px-3 py-1 rounded"}
        >
          {cat}
        </button>
      ))}
    </div>
  );
}
 
export default function ProductList({ products }: { products: Product[] }) {
  const [selectedCategory, setSelectedCategory] = useState("All");
  const [searchQuery, setSearchQuery] = useState("");
 
  const categories = ["All", ...new Set(products.map((p) => p.category))];
 
  const filteredProducts = products.filter((p) => {
    const matchesCategory = selectedCategory === "All" || p.category === selectedCategory;
    const matchesSearch = p.name.toLowerCase().includes(searchQuery.toLowerCase());
    return matchesCategory && matchesSearch;
  });
 
  return (
    <div>
      <input
        type="text"
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="Search products..."
        className="border rounded px-3 py-2 mb-4 w-full"
      />
      <CategoryFilter
        categories={categories}
        selected={selectedCategory}
        onSelect={setSelectedCategory}
      />
      <div className="grid grid-cols-3 gap-4">
        {filteredProducts.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}

المشكلة بدون React Compiler

بدون المترجم، كل ضغطة مفتاح في حقل البحث تسبب:

  1. إعادة تصيير ProductList (تغيرت الحالة)
  2. إعادة إنشاء مصفوفة categories (مرجع جديد كل تصيير)
  3. إعادة تصيير CategoryFilter (مرجع جديد لخاصية categories)
  4. إعادة إنشاء دالة onSelect (مرجع دالة جديد)
  5. إعادة تصيير جميع مكونات ProductCard

الإصلاح اليدوي القديم

قبل React Compiler، كنت ستضيف يدوياً:

// حفظ كل شيء يدوياً — مرهق وعرضة للخطأ
const categories = useMemo(
  () => ["All", ...new Set(products.map((p) => p.category))],
  [products]
);
 
const filteredProducts = useMemo(
  () => products.filter((p) => { /* ... */ }),
  [products, selectedCategory, searchQuery]
);
 
const handleSelect = useCallback(
  (cat: string) => setSelectedCategory(cat),
  []
);
 
// بالإضافة إلى تغليف المكونات الفرعية في React.memo()
const ProductCard = React.memo(function ProductCard({ product }: { product: Product }) {
  // ...
});

ما يفعله React Compiler تلقائياً

مع تفعيل React Compiler، يتم تحويل الكود النظيف الأصلي (بدون useMemo/useCallback) تلقائياً وقت البناء. المترجم:

  1. يحفظ حساب categories
  2. يحفظ فلترة filteredProducts
  3. يحفظ دالة onSelect
  4. يخزّن عناصر JSX التي لم تتغير
  5. يتخطى إعادة تصيير CategoryFilter عندما يتغير searchQuery فقط

أنت تكتب كوداً نظيفاً. المترجم يتولى الأداء.

الخطوة 6: التحقق من مخرجات المترجم

للتأكد من عمل المترجم، ثبّت React DevTools وابحث عن شارة "Memo":

# في متصفحك، ثبّت إضافة React DevTools
# ثم افتح DevTools ← تبويب Components

المكونات المحسّنة بواسطة React Compiler تعرض شارة "Memo ✨" في React DevTools. هذا يؤكد أن المترجم قد حفظ هذا المكون تلقائياً.

يمكنك أيضاً التحقق من المخرجات المترجمة مباشرة. أضف هذا إلى next.config.ts للتصحيح:

const nextConfig: NextConfig = {
  experimental: {
    reactCompiler: {
      // تسجيل نتائج الترجمة
      logger: {
        logEvent(filename, event) {
          console.log(`[React Compiler] ${filename}:`, event);
        },
      },
    },
  },
};

شغّل خادم التطوير وتحقق من مخرجات الطرفية:

npm run dev

سترى سجلات توضح المكونات التي تمت ترجمتها والتحسينات المطبقة.

الخطوة 7: قياس الأداء

لنقيس التأثير الفعلي. أنشئ مكون اختبار أداء:

// app/perf-test/page.tsx
"use client";
 
import { useState, useEffect, useRef } from "react";
 
function ExpensiveChild({ value }: { value: number }) {
  // محاكاة تصيير مكلف
  const start = performance.now();
  while (performance.now() - start < 2) {
    // انتظار مشغول لمدة 2 مللي ثانية
  }
  return <div className="p-2 border rounded">{value}</div>;
}
 
export default function PerfTest() {
  const [count, setCount] = useState(0);
  const [unrelated, setUnrelated] = useState(0);
  const renderCount = useRef(0);
  const startTime = useRef(0);
 
  useEffect(() => {
    renderCount.current++;
  });
 
  const items = Array.from({ length: 100 }, (_, i) => i);
 
  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-4">اختبار الأداء</h1>
      <div className="flex gap-4 mb-4">
        <button
          onClick={() => {
            startTime.current = performance.now();
            setCount((c) => c + 1);
          }}
          className="bg-blue-500 text-white px-4 py-2 rounded"
        >
          زيادة المرتبط: {count}
        </button>
        <button
          onClick={() => {
            startTime.current = performance.now();
            setUnrelated((u) => u + 1);
          }}
          className="bg-gray-500 text-white px-4 py-2 rounded"
        >
          زيادة غير المرتبط: {unrelated}
        </button>
      </div>
      <p>عدد مرات التصيير: {renderCount.current}</p>
      <div className="grid grid-cols-10 gap-2">
        {items.map((i) => (
          <ExpensiveChild key={i} value={i + count} />
        ))}
      </div>
    </div>
  );
}

بدون React Compiler: النقر على "زيادة غير المرتبط" يعيد تصيير جميع مكونات ExpensiveChild الـ 100 (تأخير حوالي 200 مللي ثانية).

مع React Compiler: النقر على "زيادة غير المرتبط" يتخطى إعادة تصيير جميع ExpensiveChild لأن خصائصها لم تتغير (استجابة فورية تقريباً).

استخدم تبويب الأداء في Chrome DevTools للتسجيل والمقارنة:

  1. افتح DevTools، اذهب إلى تبويب Performance
  2. انقر Record
  3. انقر "زيادة غير المرتبط" 5 مرات
  4. أوقف التسجيل
  5. قارن مخططات اللهب مع وبدون المترجم

الخطوة 8: التعامل مع الحالات الخاصة

إلغاء الاشتراك في الترجمة

إذا لم يعمل مكون بشكل صحيح بعد الترجمة، يمكنك استثناءه:

// أضف توجيه "use no memo" لتخطي الترجمة
"use no memo";
 
export default function LegacyComponent() {
  // لن يتم ترجمة هذا المكون
  // مفيد للمكونات التي تعتمد على هوية المرجع
}

الاشتراك (وضع التعليق التوضيحي)

إذا كنت تستخدم compilationMode: "annotation"، اشترك في مكونات محددة:

// أضف توجيه "use memo" لتفعيل الترجمة
"use memo";
 
export default function OptimizedComponent() {
  // فقط هذا المكون يتم ترجمته
}

الانتهاكات الشائعة

سيتخطى المترجم المكونات التي تنتهك قواعد React. المشكلات الشائعة:

1. تعديل الخصائص أو الحالة مباشرة:

// خطأ — المترجم لا يمكنه تحسين هذا
function BadComponent({ items }: { items: string[] }) {
  items.sort(); // تعديل الخصائص!
  return <ul>{items.map((item) => <li key={item}>{item}</li>)}</ul>;
}
 
// صحيح — أنشئ مصفوفة جديدة
function GoodComponent({ items }: { items: string[] }) {
  const sorted = [...items].sort();
  return <ul>{sorted.map((item) => <li key={item}>{item}</li>)}</ul>;
}

2. استدعاء الخطافات بشكل مشروط:

// خطأ — يجب استدعاء الخطافات بشكل غير مشروط
function BadComponent({ show }: { show: boolean }) {
  if (show) {
    const [value, setValue] = useState(0); // خطاف مشروط!
  }
}
 
// صحيح — استدعِ الخطافات دائماً في المستوى الأعلى
function GoodComponent({ show }: { show: boolean }) {
  const [value, setValue] = useState(0);
  if (!show) return null;
  return <div>{value}</div>;
}

3. استخدام المراجع أثناء التصيير:

// خطأ — قراءة المراجع أثناء التصيير تكسر الحفظ
function BadComponent() {
  const ref = useRef(0);
  ref.current++; // تأثير جانبي أثناء التصيير!
  return <div>{ref.current}</div>;
}
 
// صحيح — استخدم المراجع في التأثيرات أو معالجات الأحداث
function GoodComponent() {
  const ref = useRef(0);
  useEffect(() => {
    ref.current++;
  });
  return <div>تحقق من الطرفية</div>;
}

الخطوة 9: ترحيل مشروع قائم

للمشاريع القائمة، اتبع استراتيجية الترحيل التدريجي:

المرحلة 1: أضف إضافة ESLint أولاً

npm install -D eslint-plugin-react-compiler

شغّل المدقق وأصلح جميع الانتهاكات قبل تفعيل المترجم.

المرحلة 2: فعّل في وضع التعليق التوضيحي

// next.config.ts
const nextConfig: NextConfig = {
  experimental: {
    reactCompiler: {
      compilationMode: "annotation",
    },
  },
};

أضف "use memo" لبعض المكونات واختبر بدقة.

المرحلة 3: التبديل إلى وضع الاستنتاج

بمجرد الثقة، بدّل إلى الوضع الافتراضي:

const nextConfig: NextConfig = {
  experimental: {
    reactCompiler: true,
  },
};

المرحلة 4: إزالة الحفظ اليدوي

الآن يمكنك إزالة استدعاءات useMemo وuseCallback وReact.memo اليدوية بأمان:

# ابحث عن جميع حالات الحفظ اليدوي في قاعدة الكود
grep -rn "useMemo\|useCallback\|React.memo" --include="*.tsx" --include="*.ts" src/

أزلها واحدة تلو الأخرى، مع التحقق من أن المترجم يتعامل مع كل حالة بشكل صحيح.

الخطوة 10: أفضل الممارسات

افعل

  • اكتب React نظيف ومعياري — المترجم يكافئ الكود البسيط
  • اتبع قواعد React — مكونات نقية، لا تأثيرات جانبية أثناء التصيير
  • استخدم إضافة ESLint — اكتشف الانتهاكات قبل وصولها للإنتاج
  • اختبر الأداء مع DevTools — تحقق من أن المترجم يساعد

لا تفعل

  • لا تحتفظ بالحفظ اليدوي بجانب المترجم — إنه زائد عن الحاجة
  • لا تستخدم "use no memo" كأول حل — أصلح المشكلة الأساسية بدلاً من ذلك
  • لا تعتمد على هوية المرجع لمنطق العمل — المترجم قد يغير وقت إنشاء الكائنات
  • لا تعدّل الكائنات أو المصفوفات في مكانها — أنشئ مراجع جديدة دائماً

مكونات الخادم

يفيد React Compiler بشكل أساسي مكونات العميل ("use client"). مكونات الخادم تُصيَّر مرة واحدة على الخادم، لذا الحفظ له تأثير أقل. ركّز جهود التحسين على الكود التفاعلي من جانب العميل.

استكشاف الأخطاء وإصلاحها

أخطاء البناء بعد تفعيل المترجم

إذا فشل البناء، تحقق من رسالة الخطأ. الأسباب الشائعة:

  1. بنية غير مدعومة — تأكد من استخدام أنماط React القياسية
  2. تعارض مكتبة خارجية — بعض المكتبات تستخدم أنماطاً غير قياسية؛ استخدم "use no memo" على المكونات المغلّفة
  3. إصدار React قديم — حدّث إلى React 19+

المكون يتصرف بشكل مختلف

إذا عمل مكون بشكل مختلف بعد الترجمة:

  1. أضف "use no memo" لعزل المشكلة
  2. تحقق مما إذا كان المكون يعتمد على هوية المرجع
  3. تأكد من اتباعه لقواعد React
  4. أبلغ عن المشكلة في مستودع React Compiler على GitHub

لا تحسّن في الأداء

إذا لم تلاحظ تحسينات:

  1. مكوناتك قد تكون فعّالة بالفعل
  2. قد يكون عنق الزجاجة في الشبكة أو جلب البيانات، وليس التصيير
  3. استخدم React DevTools Profiler لتحديد عنق الزجاجة الفعلي

الخلاصة

يغيّر React Compiler طريقة كتابة تطبيقات React. بدلاً من تشتيت الكود بـ useMemo وuseCallback وReact.memo، تكتب مكونات نظيفة وبسيطة وتدع المترجم يتولى التحسين تلقائياً.

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

  • سطر واحد في next.config.ts يفعّل التحسين التلقائي
  • إضافة ESLint تكتشف المشكلات قبل وصولها للإنتاج
  • الكود النظيف يفوز — المترجم يكافئ React المعياري
  • الاعتماد التدريجي مدعوم من خلال وضع التعليق التوضيحي
  • تحسينات الأداء حقيقية — خاصة للواجهات المعقدة مع إعادة تصيير متكررة

مستقبل أداء React تلقائي. ابدأ باستخدام React Compiler اليوم ودع الكود يكون نظيفاً وسريعاً في آن واحد.

الخطوات التالية


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على برمجة R للمعلوماتية الحيوية: إتقان فئات الكائنات.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة