🌐 Detecting your location…

كيفية إصلاح خطأ “عدد كبير جدًا من عمليات إعادة العرض” في React: الحل الكامل

⏱️2 min read  ·  427 words

Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.يخبرك React أن العرض تسبب في تحديث الحالة، والذي تسبب في العرض، والذي تسبب في التحديث. يكون السبب دائمًا واحدًا من أربعة أنماط، ولكل منها حل واضح.

ما الذي يحدث بالفعل

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

function Counter() {
  const [count, setCount] = useState(0);

  setCount(count + 1);   // runs during render -> triggers render -> runs again

  return <div>{count}</div>;
}

رسالة الخطأ دقيقة بشكل غير عادي هنا: هناك شيء ما في مسار العرض الخاص بك يقوم بتحديث الحالة في كل مرة يتم تشغيله.

السبب الأول: استدعاء المعالج بدلاً من تمريره

إلى حد بعيد الإصدار الأكثر شيوعًا، والذي يلفت انتباه الجميع مرة واحدة على الأقل.

function App() {
  const [count, setCount] = useState(0);

  // ❌ setCount(count + 1) is CALLED during render.
  // Its return value (undefined) becomes the onClick handler.
  return <button onClick={setCount(count + 1)}>Increment</button>;
}

onClick يحتاج إلى وظيفة للاتصال لاحقا. الكتابةsetCount(...) ينفذه على الفور. لفها في دالة سهمية بحيث يتم تمرير دالة بدلاً من النتيجة.

// ✅ A function is passed; it runs on click.
<button onClick={() => setCount(count + 1)}>Increment</button>

// ✅ Or reference an existing function without calling it.
<button onClick={handleClick}>Increment</button>      // no parentheses

// ❌ This calls handleClick during render.
<button onClick={handleClick()}>Increment</button>

القاعدة:الأقواس بعد اسم المعالج في JSX تعني أنه يعمل أثناء التصيير. إذا كنت بحاجة إلى تمرير الحجج، قم بتغليفها.

<button onClick={() => handleDelete(item.id)}>Delete</button>

السبب 2: استخدام التأثير بدون صفيف تبعية

يتم تنفيذ التأثير الذي لا يحتوي على وسيطة ثانية بعد كل عملية تصيير. إذا تم تعيين الحالة، فإن تغيير الحالة يؤدي إلى تشغيل عرض آخر، والذي يقوم بتشغيل التأثير مرة أخرى.

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

  // ❌ No dependency array — runs after every render, forever.
  useEffect(() => {
    fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
  });
}
// ✅ Re-runs only when userId changes.
useEffect(() => {
  fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
}, [userId]);

يختلف حذف المصفوفة عن تمرير مصفوفة فارغة. لا يوجد مصفوفة تعني “كل عرض”؛ [] يعني “مرة واحدة على الجبل”.

السبب 3: التبعية التي تغير كل عرض

تتم مقارنة الكائنات والمصفوفات والوظائف حسب المرجع. كل عرض جديد يعني أن التبعية تبدو دائمًا متغيرة.

function Search({ query }) {
  const [results, setResults] = useState([]);

  // New object identity on every render.
  const options = { query, limit: 20 };

  useEffect(() => {
    search(options).then(setResults);
  }, [options]);   // ❌ always "changed" -> loop
}

تعتمد على البدائيات، التي تقارن من حيث القيمة.

useEffect(() => {
  search({ query, limit: 20 }).then(setResults);
}, [query]);   // ✅ string compares by value

عندما يجب أن يوجد الكائن خارج التأثير، احفظه.

const options = useMemo(() => ({ query, limit: 20 }), [query]);

useEffect(() => {
  search(options).then(setResults);
}, [options]);   // ✅ identity is stable until query changes

السبب 4: تحديد الحالة لاشتقاق قيمة

يؤدي استخدام الحالة بالإضافة إلى التأثير لحساب شيء ما من القيم الموجودة إلى إنشاء دورة غير ضرورية وعرض إضافي.

function Cart({ items }) {
  const [total, setTotal] = useState(0);

  // ❌ Unnecessary, and loops if the dependency is wrong.
  useEffect(() => {
    setTotal(items.reduce((sum, i) => sum + i.price, 0));
  });

  return <p>Total: {total}</p>;
}

يجب حساب أي شيء قابل للحساب من الخاصيات أو الحالة أثناء التصيير.

function Cart({ items }) {
  // ✅ No state, no effect, no possible loop.
  const total = items.reduce((sum, i) => sum + i.price, 0);
  return <p>Total: {total}</p>;
}

إذا كانت العملية الحسابية باهظة الثمن حقًا، فقم بإدراجها فيuseMemo — ولكن لا تزال لا تضعه في الدولة.

const total = useMemo(
  () => items.reduce((sum, i) => sum + i.price, 0),
  [items]
);

متغير العرض الشرطي

يعد تعيين الحالة داخل الشرط أثناء العرض هو نفس الخطأ المخفي.

function Form({ initialValue }) {
  const [value, setValue] = useState('');

  // ❌ Still a state update during render.
  if (initialValue && !value) {
    setValue(initialValue);
  }
}

تهيئة الحالة بشكل صحيح بدلاً من ذلك. إذا كان حساب القيمة الأولية مكلفًا، فقم بتمرير دالة بحيث يتم تشغيلها مرة واحدة فقط.

const [value, setValue] = useState(initialValue ?? '');

// Lazy initialiser — the function runs on the first render only.
const [rows, setRows] = useState(() => parseLargeCsv(raw));

إذا كان يجب إعادة تعيين القيمة عند تغيير الخاصية، فإن الحل الاصطلاحي هوkey على المكون الذي يعيد تركيبه بحالة جديدة.

<Form key={userId} initialValue={user.name} />

العثور على الجاني

عندما لا يكون السبب واضحًا، قم بتسجيل مكان إنشاء العروض.

function MyComponent(props) {
  console.count('MyComponent render');
  console.log('props:', props);
  // ...
}

يخبرك React DevTools Profiler مع تمكين “تسجيل سبب عرض كل مكون” بالدعامة أو الحالة التي تم تغييرها في كل مرور. بالنسبة لحلقات التأثير، قم بتسجيل الدخول داخل التأثير وداخل أداة الضبط لرؤية الدورة مباشرة.

useEffect(() => {
  console.log('effect ran, deps:', { userId, options });
}, [userId, options]);

مرجع سريع

الأعراض السبب المحتمل اصلاح
أخطاء فورية عند التحميل تم استدعاء المعالج أثناء العرض التفاف في وظيفة السهم
حلقات بعد تحميل البيانات التأثير يفتقد مصفوفة تبعية أضف[] أو الإقلاع الصحيح
الحلقات بالرغم من عمليات الإنطلاق الصحيحة تبعية الكائن أو الوظيفة استخدم البدائيات،useMemoأوuseCallback
عرض إضافي عند كل تغيير القيمة المشتقة المخزنة في الحالة الحساب أثناء العرض
حلقات على الشرط setState داخل فرع العرض تهيئة الحالة أو استخدمkey

الأسئلة المتداولة

س: لماذا يحدث هذا في بعض الأحيان فقط؟
ج: الحلقات التي تعتمد على البيانات تحتاج إلى وصول البيانات أولاً. الخطأ موجود منذ البداية ولكن يتم تشغيله فقط بمجرد حل عملية الجلب وتعيين الحالة.

س: هل من المقبول على الإطلاق تعيين الحالة أثناء العرض؟
ج: تسمح React بحالة واحدة ضيقة – ضبط الحالة أثناء التصيير عندما تتغير الخاصية، وتحديث نفس المكون فقط، وحمايتها من خلال المقارنة. نادرا ما تكون الإجابة الأفضل. تفضل الحوسبة أثناء العرض أو باستخدامkey.

س: هل يحدث هذا في مكونات الفصل؟
ج: نعم. اتصالsetState in render أو دون قيد أو شرط فيcomponentDidUpdate تنتج نفس الحلقة.

س: التأثير الخاص بي يحتوي على مصفوفة فارغة ولكن لا يزال يتكرر. لماذا؟
ج: ثم تكون الحلقة في مكان آخر – في أغلب الأحيان يتم استدعاء المعالج أثناء العرض، أو يقوم أحد الوالدين بإعادة تثبيت الطفل لأنهkey يغير كل تقديم.

س: هل سيصلح مترجم React هذا الأمر بالنسبة لي؟
ج: يقوم بإزالة فئة الحفظ الخاصة بالمشكلة عن طريق التعامل مع الهوية تلقائيًا. ولا يُصلح استدعاء المعالج أثناء التصيير، وهو خطأ مباشر في JSX.

الخلاصة

“عدد كبير جدًا من عمليات إعادة العرض” يعني دائمًا أنه يتم تحديث الحالة كجزء من العرض. تحقق من الأسباب الأربعة بالترتيب:تم استدعاء المعالج بدلاً من تمريره في JSX، أuseEffectمع عدم وجود مصفوفة تبعية، تبعية كائن أو وظيفة تغير الهوية في كل عرض، والقيم المشتقة مخزنة في الحالة بدلاً من حسابها. القاعدة الأساسية بسيطة – يجب أن يكون العرض عملية حسابية خالصة، ويجب أن ينشأ كل تحديث للحالة من معالج حدث أو تأثير، وليس من العرض نفسه أبدًا.

MD Rafikul Islam

Written by

MD Rafikul Islam is a software developer and the editor of TechPulse. He writes about developer tooling, hardware, and the practical decisions that come up in day-to-day engineering work — which laptop to buy, which framework to commit to, why a build broke at 2am. He tests the tools he writes about and says plainly when something is not worth the money. Corrections and corrections requests are welcome at rony.yf25@gmail.com.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা