React

تقنيات تحسين أداء React

استراتيجيات عملية لتسريع تطبيقات React الخاصة بك وتحسين Core Web Vitals.

١٥ سبتمبر ٢٠٢٥
8 دقيقة قراءة
بواسطة useLines Team
ReactPerformanceCore Web VitalsOptimization
Illustration for تقنيات تحسين أداء React

أداء React، باختصار

ركز على عدد أقل من عمليات العرض، وعدد أقل من عقد DOM، وعدد أقل من البايتات، وتأجيل العمل غير الحرج.

قم بالقياس أولاً

  • استخدم لوحة الأداء ومحلل React
  • تتبع Core Web Vitals (LCP, INP, CLS)

قلل من عمليات إعادة العرض

  • استقرار الدعائم مع useMemo/useCallback فقط حيث يظهر التوصيف اضطرابًا
  • قسم السياق الكبير أو استخدم خطافات التحديد
  • فضل واجهة المستخدم المشتقة على تكرار الحالة

اعرض أقل

  • افتراض القوائم الطويلة (react-window)
  • تقسيم الكود للمسارات/الأدوات الثقيلة (React.lazy + Suspense)

شحن عدد أقل من البايتات

  • تحسين الصور (الأحجام/srcset، التنسيقات الحديثة، التحميل الكسول)
  • تخزين استجابات API مؤقتًا؛ تأخير المدخلات؛ الضغط عبر الشبكة

حافظ على سرعة التفاعلات

  • useTransition/useDeferredValue للتحديثات المكلفة أثناء الكتابة
  • تجنب اضطراب التخطيط؛ فضل transform/opacity للرسوم المتحركة

قائمة التحقق: دعائم مستقرة، افتراض القوائم الكبيرة، تحسين الصور، تقسيم الكود، التحقق باستخدام Web Vitals.

const ExpensiveList = memo(({ items }) => {
  const sorted = useMemo(() => items.slice().sort(), [items]);
  const handleClick = useCallback((id) => console.log(id), []);
  return sorted.map((id) => (
    <button key={id} onClick={() => handleClick(id)}>
      {id}
    </button>
  ));
});

افتراض القوائم الطويلة

استخدم مكتبات النوافذ للقوائم التي تزيد عن 100 عنصر.

تقسيم الكود

قم بالتحميل الكسول للمسارات والمكونات غير الحرجة باستخدام React.lazy و Suspense.

تحسين الصور

قدم صورًا سريعة الاستجابة، واستخدم تنسيقات حديثة (WebP/AVIF)، وقم بالتحميل الكسول للصور خارج الشاشة.

قم بالقياس أولاً

قم بالتوصيف باستخدام محلل React DevTools ولوحة أداء المتصفح قبل تغيير الكود.

قم بتوصيف React بالطريقة الصحيحة

ابدأ بعلامة تبويب الأداء ومحلل React:

  1. سجل التفاعلات التي تبدو بطيئة (تغيير المسار، تصفية القائمة، فتح النوافذ المنبثقة).
  2. حدد المكونات ذات أوقات العرض الطويلة وعدد الالتزامات المرتفع.
  3. تحقق من "إعادة العرض المتتالية" حيث يعيد الأصل عرض العديد من الأبناء.

إذا أظهر الرسم البياني للهب العديد من عمليات العرض دون تغييرات في الحالة، فمن المحتمل أن يكون لديك دعائم غير مستقرة أو إعادة رسم للسياق.

منع عمليات إعادة العرض غير الضرورية

  • مرر مراجع مستقرة: انقل الكائنات/الدوال المضمنة للخارج أو قم بتغليفها بـ useMemo/useCallback عندما تسبب تكلفة إعادة عرض حقيقية.
  • قسم السياق: تفرض السياقات الكبيرة إعادة عرض واسعة؛ قسمها حسب الاهتمام أو استخدم السياقات المستندة إلى المحدد.
  • فضل واجهة المستخدم المشتقة على الحالة المشتقة: احسب واجهة المستخدم من الحالة الأولية بدلاً من تخزين نسخ متعددة.
// سيء: كائن جديد في كل عرض ← إعادة عرض الابن
<Chart options={{ color: theme.primary }} />;

// جيد: قيمة مذكرة مستقرة
const chartOptions = useMemo(() => ({ color: theme.primary }), [theme.primary]);
<Chart options={chartOptions} />;

React.memo ومتى تستخدمه

يمكن لـ React.memo تقليل عمليات إعادة عرض المكونات النقية. يساعد أكثر عندما:

  • نادرًا ما تتغير الدعائم
  • عمل المكون مكلف (التنسيق، DOM الثقيل، القوائم الكبيرة)

احذر من الإفراط في استخدامه - فالتذكر له تكلفة مقارنة. أضفه حيث يظهر التوصيف فائدة.

const Card = React.memo(function Card({ title, children }) {
  return (
    <section className="blog-card rounded-xl p-6">
      <h3 className="font-bold mb-2">{title}</h3>
      {children}
    </section>
  );
});

افتراض القوائم الطويلة حقًا

بالنسبة لـ 100+ عنصر، اعرض فقط ما هو مرئي. تقلل مكتبات مثل react-window أو react-virtualized من عقد DOM بشكل كبير.

npm i react-window
import { FixedSizeList as List } from "react-window";

const Row = ({ index, style, data }) => (
  <div style={style}>{data[index].title}</div>
);

export function PostsVirtualized({ items }) {
  return (
    <List
      height={480}
      itemCount={items.length}
      itemSize={56}
      width={"100%"}
      itemData={items}
    >
      {Row}
    </List>
  );
}

تحسين الصور بقوة

  • قم دائمًا بتضمين width و height جوهريين أو نسب الحاوية لتقليل CLS.
  • استخدم loading="lazy" و decoding="async" (موجود بالفعل في LazyImage لهذا المشروع).
  • قدم أحجامًا متعددة باستخدام srcset/sizes عندما يكون ذلك ممكنًا.
<img
  src="/img/hero-1280.jpg"
  srcset="
    /img/hero-640.jpg   640w,
    /img/hero-960.jpg   960w,
    /img/hero-1280.jpg 1280w
  "
  sizes="(max-width: 768px) 100vw, 768px"
  alt="Hero"
  loading="lazy"
  decoding="async"
/>

تقسيم الكود لواجهة المستخدم غير الحرجة

قم بالتحميل الكسول للأدوات الثقيلة والحزم على مستوى المسار:

const Comments = React.lazy(() => import("./Comments"));

export function PostFooter() {
  return (
    <React.Suspense fallback={<div>Loading comments…</div>}>
      <Comments />
    </React.Suspense>
  );
}

تعديلات الشبكة ووقت التشغيل

  • تخزين استجابات API مؤقتًا؛ تأخير الاستعلامات التي تعتمد على الإدخال.
  • تجنب JSON الكبير في الذاكرة؛ قم بالتدفق حيثما أمكن.
  • استخدم CDN للأصول الثابتة؛ فضل ضغط Brotli.

ربط التحسينات بـ Web Vitals

تتبع LCP و INP و CLS. يتضمن هذا المشروع بالفعل أداة أداء (src/utils/performance.js). تأكد من تكوين خطافات التحليلات حتى تتمكن من التحقق من التأثير.

جدولة العمل للحفاظ على سرعة التفاعلات

استخدم الميزات المتزامنة لمنع تجميد واجهة المستخدم أثناء ضغطات المفاتيح والتنقل.

import { useDeferredValue, useTransition } from "react";

export function Search({ list }) {
  const [term, setTerm] = React.useState("");
  const [isPending, startTransition] = useTransition();

  // Defer filtering to keep typing responsive
  const deferredTerm = useDeferredValue(term);
  const results = React.useMemo(
    () => list.filter((x) => x.includes(deferredTerm)),
    [list, deferredTerm]
  );

  function onChange(e) {
    const value = e.target.value;
    startTransition(() => setTerm(value));
  }

  return (
    <div>
      <input value={term} onChange={onChange} />
      {isPending && <span>Updating…</span>}
      <ul>
        {results.map((r) => (
          <li key={r}>{r}</li>
        ))}
      </ul>
    </div>
  );
}

تجنب اضطراب التخطيط

قم بتجميع القراءات والكتابات وتقليل قياسات التخطيط المتزامنة في المسارات الساخنة.

// سيء: يفرض التخطيط على كل حركة الماوس
element.addEventListener("mousemove", () => {
  const { left } = element.getBoundingClientRect(); // قراءة
  element.style.transform = `translateX(${left}px)`; // كتابة
});

// أفضل: requestAnimationFrame وقراءات مخزنة مؤقتًا
let frame = 0;
element.addEventListener("mousemove", () => {
  if (frame) return;
  frame = requestAnimationFrame(() => {
    const left = cachedLeft; // from a scheduled read
    element.style.transform = `translateX(${left}px)`;
    frame = 0;
  });
});

استخدم CSS حيثما أمكن (مثل transform، opacity) وأشر بـ will-change للعناصر المتحركة للغاية.

حدود جلب البيانات و Suspense

قم بتغليف الأشجار الفرعية البطيئة في Suspense مع بدائل خفيفة الوزن للحفاظ على تفاعل الصفحة.

const Pricing = React.lazy(() => import("./Pricing"));

export function Page() {
  return (
    <React.Suspense fallback={<div className="h-32 animate-pulse" />}>
      <Pricing />
    </React.Suspense>
  );
}

مزالق الترطيب

  • تجنب قراءة window/document أثناء العرض الأولي؛ قم بالحماية باستخدام التأثيرات أو فحوصات البيئة.
  • حافظ على اتساق ترميز الخادم والعميل (لا توجد معرفات عشوائية بدون بذور مستقرة).
  • قدم width/height صريحين للوسائط لتجنب تحولات التخطيط بعد الترطيب.

قائمة التحقق

  • دعائم مستقرة وتذكر للمسارات الساخنة
  • قوائم افتراضية للمجموعات الكبيرة
  • صور محسنة بأحجام مناسبة وتحميل كسول
  • تقسيم الكود الاستراتيجي للمكونات الثقيلة
  • مراقبة Web Vitals قبل/بعد التغييرات

الخلاصة

قم بالقياس أولاً، وقم بالتحسين حيث يؤلم، وتحقق باستخدام أدوات التوصيف. تأتي معظم المكاسب من تقليل العمل (العروض، DOM، البايتات) وتأجيل ما لا يحتاجه المستخدم على الفور.

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