Menu
flag Ar iconالعربيةdown icon

العرض في React: ما الذي يطلق إعادة العرض ولماذا

يُعاد عرض مكوّن React عندما تتغير حالته، أو عندما يُعرض أبوه، أو عندما يتغير سياق يقرؤه. تعلّم الفرق بين العرض والإثبات، وما المقصود حقًا بـ DOM الافتراضي، وكيف توقف مرات العرض التي لا تحتاجها.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

يُعاد عرض مكوّن React عندما تتغير حالته الخاصة، أو عندما يُعاد عرض أبيه، أو عندما يتغير سياق يقرؤه. والعرض يعني أن React تستدعي دالة مكوّنك من جديد لتحصل على JSX جديدة؛ ثم لا تحدّث إلا أجزاء الصفحة التي تغيّرت فعلًا.

افتح Console أسفل المعاينة وانقر الزر. تسجّل المكوّنات الثلاثة كلها، بما فيها Title، الذي لا يأخذ أي props إطلاقًا. لقد عُرض لأن أباه عُرض.

ما الذي يطلق العرض

تعرض React مكوّنًا لهذه الأسباب بالضبط:

  1. العرض الأول. يبدأ التطبيق، أو يظهر المكوّن في الشجرة لأول مرة.
  2. تغيّرت حالته. استدعيت دالة ضبط من useState أو dispatch من useReducer بقيمة جديدة.
  3. عُرض أبوه. افتراضيًا، عندما يُعرض مكوّن، يُعرض كل مكوّن يُرجعه أيضًا، وصولًا إلى الأسفل.
  4. تغيّر سياق يقرؤه. المكوّن الذي يستدعي useContext(SomeContext) يُعرض عندما يمرر أقرب مزوّد value جديدة (راجع useContext).

من المعتقدات الشائعة أن المكوّن يُعرض «لأن props الخاصة به تغيّرت». وهذا يعكس السبب. props وسائط يمررها الأب أثناء عرضه، فلا يمكن أن تصل props جديدة إلا عندما يُعرض الأب. وعرض الأب يكفي وحده: Title أعلاه ليست له props ومع ذلك يُعرض في كل مرة.

ضبط الحالة على القيمة التي تحملها أصلًا (بالمقارنة عبر Object.is) لا يبدأ عرضًا للأبناء. قد تظل React تستدعي ذلك المكوّن الواحد مرة قبل أن تلاحظ أن شيئًا لم يتغير، لكنها ترمي النتيجة.

العرض والإثبات

يمر كل تحديث بمرحلتين:

  • العرض. تستدعي React مكوّناتك. فتُرجع JSX، وهي مجرد كائنات تصف ما يجب أن تُظهره الشاشة. لا يتغير شيء في الصفحة بعد، ولهذا يجب أن يكون العرض نقيًا: لا كتابة في DOM، ولا طلبات، ولا تغيير لمتغيرات خارج المكوّن.
  • الإثبات (commit). تقارن React المخرجات الجديدة بالسابقة وتطبّق الفروق على DOM: تدرج أو تحذف أو تحدّث العقد التي تغيّرت فقط. ثم يرسم المتصفح، وبعد ذلك تشغّل React تأثيراتك.

يُعرض المثال أدناه مع كل نقرة، لكن React تحتفظ بعنصر <input> نفسه. ويعمل تأثير بعد كل إثبات ويتحقق من ذلك.

اكتب شيئًا في الحقل وانقر بضع مرات. يبقى النص الذي كتبته، لأن React لم تستبدل الحقل أبدًا: تغيير DOM الوحيد مع كل نقرة هو الرقم داخل <p>. ويُظهر السجل أيضًا الترتيب، render أولًا ثم التأثير بعد الإثبات.

DOM الافتراضي ببساطة

«DOM الافتراضي» هو الاسم الشائع للكائنات التي تُرجعها مكوّناتك. يتحول <p>Items: {count}</p> إلى استدعاء ينشئ كائنًا مثل { type: 'p', props: { children: ['Items: ', 1] } }. بعد العرض، تمر React على الشجرة الجديدة من هذه الكائنات بجانب السابقة، وحيث يتطابق النوع والموضع تحتفظ بعقدة DOM الموجودة ولا تحدّث إلا السمات والنصوص المتغيرة. تُسمى هذه المقارنة التوفيق (reconciliation).

المصطلح فضفاض لسببين. لا تحتفظ React بنسخة ثانية من DOM وتقارن بين نسختين منه: بل تقارن كائنات العناصر بشجرتها الداخلية من المكوّنات (شجرة fiber). والعملية نفسها تشغّل أهدافًا لا DOM لها إطلاقًا، مثل React Native. وتتجنب وثائق React هذه العبارة في الغالب وتتحدث عن العرض والإثبات بدلًا منها. وما يهم عمليًا هو النتيجة: العرض رخيص مقارنة بعمل DOM، لأن معظم مرات العرض تنتهي بتحديث صغير لـ DOM أو بلا تحديث.

تنتج قاعدتان من طريقة عمل المقارنة. نوع عنصر مختلف في الموضع نفسه (<div> استُبدل بـ <section>، أو ComponentA بـ ComponentB) يدمّر الشجرة الفرعية القديمة وحالتها. وفي القوائم، يخبر key React أي عنصر هو أي عنصر، فتستطيع نقل العقد بدلًا من إعادة بنائها.

إيقاف مرات العرض التي لا تحتاجها

معظم مرات العرض الإضافية لا تكلّف شيئًا يمكنك ملاحظته. وعندما يضر أحدها فعلًا، مثل قائمة كبيرة تُعرض مع كل ضغطة مفتاح في مكان آخر من الصفحة، فجرّب هذه بالترتيب.

انقل الحالة إلى الأسفل

إذا كان جزء صغير واحد من الشاشة فقط يستخدم قطعة من الحالة، فاحتفظ بتلك الحالة في مكوّن لا يغلّف إلا ذلك الجزء. هذه النسخة تعيد عرض ProductList مع كل ضغطة مفتاح:

export default function App() {
    const [text, setText] = useState('');
    return (
        <>
            <input value={text} onChange={(e) => setText(e.target.value)} />
            <ProductList />
        </>
    );
}

انقل الحقل وحالته إلى مكوّن خاص بهما فلا تعود القائمة داخل المكوّن الذي يُعرض:

اكتب بضعة أحرف: لا يسجّل إلا SearchBox.

مرّر children بدلًا من ذلك

أحيانًا يجب أن تعيش الحالة في مغلّف حول الجزء المكلف، مثل لوحة تبدّل تظليلًا. اجعل المغلّف يقبل children. ينشئ الأب العناصر الأبناء، ولأن الأب لا يُعرض عندما تتغير حالة المغلّف، تكون تلك العناصر الكائنات نفسها كما كانت، فتتخطاها React.

انقر المبدّل فلا يسجّل إلا Highlighter. انقل الآن <Article /> إلى داخل JSX الخاصة بـ Highlighter مكان {children}، فتسجّل كل نقرة render Article أيضًا.

الحفظ (memoization)

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

import { memo } from 'react';

const ProductList = memo(function ProductList({ category }) {
    // skipped while category stays the same
});

يستطيع React Compiler إضافة هذا النوع من الحفظ لك وقت البناء، لكن إعادة هيكلة الحالة ما زالت أول ما تجربه: فهي تزيل العمل بدلًا من تخزينه.

مرات العرض عادة لا بأس بها

العرض استدعاء دالة يُرجع كائنات. تشغّل React آلافًا منها بسرعة، والعرض الذي ينتج المخرجات نفسها لا يغيّر أي DOM. لا تضف memo في كل مكان «احتياطًا»: لكل مقارنة تكلفتها، وهي تجعل الكود أصعب قراءة. قِس أولًا. يُظهر Profiler في React DevTools أي المكوّنات عُرضت، ولماذا، وكم استغرق كل منها.

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

ما الذي يجعل مكوّن React يُعاد عرضه؟

ثلاثة أشياء: تتغير حالته الخاصة، أو يُعرض أبوه، أو يحصل سياق يقرؤه بـ useContext على قيمة جديدة. ليست props محفزًا مستقلًا: لا تصل props جديدة إلا لأن الأب عُرض.

هل يُعاد عرض الابن عندما يُعاد عرض الأب؟

نعم، افتراضيًا يُعرض كل مكوّن داخل أب يُعرض أيضًا، حتى لو لم تتغير props الخاصة به. تغليف الابن بـ memo يتيح لـ React تخطيه عندما تكون props الخاصة به كما كانت في المرة السابقة.

ما هو DOM الافتراضي في React؟

اسم فضفاض لشجرة كائنات JavaScript العادية (عناصر React) التي تُرجعها مكوّناتك. تقارن React الشجرة الجديدة بالسابقة ولا تغيّر إلا عقد DOM الحقيقية التي تختلف.

هل إعادة العرض سيئة للأداء؟

عادة لا. العرض استدعاء دالة ينتج كائنات، ولا تلمس React الـ DOM إلا حيث تغيّرت المخرجات. حسّن الأداء عندما يكون العرض بطيئًا بشكل قابل للقياس، مثلًا باستخدام Profiler في React DevTools.

ما الفرق بين العرض والإثبات؟

العرض هو استدعاء React لمكوّناتك لتعرف كيف يجب أن تبدو الشاشة. والإثبات (commit) هو تطبيق React للفروق على الـ DOM. العرض الذي ينتج المخرجات نفسها لا يُثبت أي تغيير في DOM.

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن