كيف تبدأ سريعًا مع VUE3.0: ابدأ
في هذا البرنامج التعليمي، سنتعلم كيفية تنفيذ Memoization في React. يعمل الحفظ على تحسين الأداء عن طريق تخزين نتائج استدعاءات الوظائف مؤقتًا وإرجاع تلك النتائج المخزنة مؤقتًا عند الحاجة إليها مرة أخرى.
سنغطي ما يلي:
تفترض هذه المقالة أن لديك فهمًا أساسيًا لمكونات الفئة والوظيفة في React.
إذا كنت تريد الاطلاع على هذه المواضيع، يمكنك الاطلاع على مكونات وخصائص وثائق React الرسمية
https://reactjs.org/docs/components-and-props.html
قبل مناقشة تفاصيل الحفظ في React، دعونا أولاً نلقي نظرة على كيفية استخدام React لـ DOM الافتراضي لعرض واجهة المستخدم.
يحتوي DOM العادي بشكل أساسي على مجموعة من العقد الموجودة على شكل شجرة. تمثل كل عقدة في DOM عنصرًا من عناصر واجهة المستخدم. كلما حدث تغيير في الحالة في التطبيق، يتم تحديث العقد المقابلة لعنصر واجهة المستخدم هذا وجميع عناصره الفرعية في شجرة DOM، مما يؤدي بعد ذلك إلى إعادة رسم واجهة المستخدم.
بمساعدة خوارزميات شجرة DOM الفعالة، يكون تحديث العقد أسرع، لكن إعادة الرسم تكون بطيئة وقد تؤثر على الأداء عندما يحتوي DOM على عدد كبير من عناصر واجهة المستخدم. لذلك، تم تقديم DOM الافتراضي في React.
هذا تمثيل افتراضي لـ DOM الحقيقي. الآن، كلما حدث أي تغيير في حالة التطبيق، لا تقوم React بتحديث DOM الحقيقي مباشرة، ولكنها تنشئ DOM افتراضيًا جديدًا. ستقوم React بعد ذلك بمقارنة DOM الافتراضي الجديد هذا مع DOM الظاهري الذي تم إنشاؤه مسبقًا، والعثور على الاختلافات (ملاحظة المترجم: أي العثور على العقد التي تحتاج إلى تحديث)، ثم إعادة الرسم.
بناءً على هذه الاختلافات، يمكن لـ DOM الافتراضي تحديث DOM الحقيقي بكفاءة أكبر. يؤدي هذا إلى تحسين الأداء لأن DOM الافتراضي لا يقوم ببساطة بتحديث عنصر واجهة المستخدم وجميع عناصره الفرعية، ولكنه يقوم بشكل فعال بتحديث التغييرات الضرورية والحد الأدنى فقط في DOM الحقيقي.
في القسم السابق، رأينا كيف تستخدم React DOM افتراضيًا لإجراء عمليات تحديث DOM بكفاءة لتحسين الأداء. في هذا القسم، سنقدم مثالاً يوضح الحاجة إلى استخدام الحفظ من أجل تحسين الأداء بشكل أكبر.
سوف نقوم بإنشاء فئة أصل تحتوي على زر يقوم بزيادة متغير يسمى count
. يقوم المكون الأصلي أيضًا باستدعاء المكون الفرعي وتمرير المعلمات إليه. أضفنا أيضًا عبارة console.log()
في طريقة render
:
//Parent.js يمتد الفصل الأصلي React.Component { منشئ (الدعائم) { سوبر(الدعائم); this.state = {count: 0}; } مقبض كليك = () => { this.setState((prevState) => { إرجاع {العدد: prevState.count + 1}; }); }; يجعل() { console.log("تقديم الوالدين"); يعود ( <div className="App"> <button onClick={this.handleClick}>الزيادة</button> <h2>{this.state.count</h2> <اسم الطفل={"جو"} /> </div> ); } } تصدير الأصل الافتراضي؛
يمكن الاطلاع على الكود الكامل لهذا المثال على CodeSandbox.
سنقوم بإنشاء فئة Child
تقبل المعلمات التي تم تمريرها بواسطة المكون الأصلي وتعرضها في واجهة المستخدم:
//Child.js فئة الطفل تمتد React.Component { يجعل() { console.log("عرض الطفل"); يعود ( <ديف> <h2>{this.props.name</h2> </div> ); } } تصدير الطفل الافتراضي؛
ستتغير قيمة count
في كل مرة ننقر فيها على الزر الموجود في المكون الأصلي. منذ أن تغيرت الحالة، يتم تنفيذ طريقة render
للمكون الأصلي.
لا تتغير المعلمات التي تم تمريرها إلى المكون الفرعي في كل مرة يتم فيها إعادة عرض المكون الأصلي، لذلك لا ينبغي إعادة عرض المكون الفرعي. ومع ذلك، عندما نقوم بتشغيل التعليمات البرمجية أعلاه ونستمر في زيادة count
، نحصل على الإخراج التالي:
عرض الأصل تقديم الطفل تقديم الوالدين تقديم الطفل تقديم الوالدين عرض فرعي
يمكنك تجربة المثال أعلاه في وضع الحماية هذا وعرض مخرجات وحدة التحكم.
من المخرجات، يمكننا أن نرى أنه عندما يتم إعادة عرض المكون الأصلي، فإن المكون الفرعي يعيد العرض حتى لو ظلت المعلمات التي تم تمريرها إلى المكون الفرعي دون تغيير. سيؤدي هذا إلى قيام DOM الظاهري للمكون الفرعي بإجراء فحص مختلف مقارنةً بـ DOM الظاهري السابق. نظرًا لعدم وجود تغييرات في المكونات الفرعية وجميع الخاصيات لم تتغير عند إعادة التصيير، فلن يتم تحديث DOM الحقيقي.
من المؤكد أن عدم تحديث DOM الحقيقي دون داعٍ يعد من فوائد الأداء، ولكن يمكننا أن نرى أنه يتم إنشاء DOM افتراضي جديد ويتم إجراء فحص الفرق حتى في حالة عدم وجود تغييرات فعلية في المكونات الفرعية. بالنسبة لمكونات React الصغيرة، تكون تكلفة الأداء ضئيلة، ولكن بالنسبة للمكونات الكبيرة، يمكن أن يكون تأثير الأداء كبيرًا. لتجنب إعادة العرض والتحقق التفاضلي لـ DOM الافتراضي، نستخدم Memoization.
في سياق تطبيقات React، الحفظ هو وسيلة يتم من خلالها إعادة تصيير المكون الأصلي، حيث يقوم المكون الفرعي بإعادة التصيير فقط عندما يعتمد على تغيير الخاصيات. إذا لم تكن هناك تغييرات في الخاصيات التي يعتمد عليها المكون الفرعي، فلن يتم تنفيذ طريقة التجسيد وسيُرجع النتائج المخزنة مؤقتًا. نظرًا لعدم تنفيذ طريقة العرض، لن يكون هناك إنشاء DOM افتراضي أو فحص تفاضلي، مما يؤدي إلى تحسين الأداء.
الآن، دعونا نرى كيفية تنفيذ الحفظ في مكونات الفئة والوظيفة لتجنب إعادة العرض غير الضرورية.
لتنفيذ عملية الحفظ في مكون الفصل، سوف نستخدم React.PureComponent. ينفِّذ React.PureComponent
التابع willComponentUpdate()، الذي يجري مقارنة سطحية state
props
ويعيد تصيير مكون React فقط في حالة تغير الخاصيات أو الحالة.
قم بتغيير المكون الفرعي إلى رمز مثل هذا:
//Child.js class Child Extends React.PureComponent { // هنا نقوم بتغيير React.Component إلى React.PureComponent يجعل() { console.log("عرض الطفل"); يعود ( <ديف> <h2>{this.props.name</h2> </div> ); } } يتم
عرض الكود الكامل لهذا المثال في وضع الحماية هذا.
يبقى المكون الأصلي دون تغيير. الآن، عندما نقوم بزيادة count
في المكون الأصلي، فإن الإخراج في وحدة التحكم يبدو كما يلي:
عرض الأصل تقديم الطفل تقديم الوالدين تقديم الأصل
بالنسبة للعرض الأول، فإنه يستدعي طريقة render
لكل من المكون الأصلي والمكون الفرعي.
لكل إعادة تصيير بعد زيادة count
، يتم استدعاء دالة render
الخاصة بالمكون الأصلي فقط. لن يتم إعادة عرض المكونات التابعة.
لتنفيذ الحفظ في مكونات الوظيفة، سوف نستخدم React.memo(). React.memo()
هو مكون ذو ترتيب أعلى (HOC) ينفذ عملًا مشابهًا لـ PureComponent
لتجنب إعادة العرض غير الضرورية.
إليك رمز مكون الوظيفة:
//Child.js وظيفة التصدير الطفل (الدعائم) { console.log("عرض الطفل"); يعود ( <ديف> <h2>{props.name}</h2> </div> ); } تصدير الافتراضي React.memo(Child); // نضيف هنا HOC إلى المكون الفرعي لتنفيذ الحفظ
وأيضًا تحويل المكون الأصلي إلى مكون دالة، كما هو موضح أدناه:
//Parent.js تصدير الوظيفة الافتراضية الأصل () { const [count, setCount] = useState(0); مقبض ثابت انقر = () => { setCount(count + 1); }; console.log("تقديم الوالدين"); يعود ( <ديف> <button onClick={handleClick}>الزيادة</button> <h2>{العد</h2> <اسم الطفل={"جو"} /> </div> ); }
يمكن رؤية الكود الكامل لهذا المثال في وضع الحماية هذا.
الآن، عندما نقوم بزيادة count
في المكون الأصلي، سيتم إخراج ما يلي إلى وحدة التحكم:
تقديم الأصل تقديم الطفل تقديم الوالدين تقديم الوالدين
العرض الأصليReact.memo()
في المثال أعلاه، نرى أنه عندما نستخدم React.memo()
HOC على مكون فرعي، لا تتم إعادة تصيير المكون الفرعي على الرغم من إعادة تصيير المكون الأصلي.
مع ذلك، هناك أمر صغير يجب ملاحظته وهو أننا إذا مررنا دالة كمعلمة إلى المكون الفرعي، فسيتم إعادة تصيير المكون الفرعي حتى بعد استخدام React.memo()
. دعونا نلقي نظرة على مثال على ذلك.
سنقوم بتغيير المكون الأصلي كما هو موضح أدناه. نضيف هنا وظيفة معالج ونمررها كمعلمة إلى المكون الفرعي:
//Parent.js تصدير الوظيفة الافتراضية الأصل () { const [count, setCount] = useState(0); مقبض ثابت انقر = () => { setCount(count + 1); }; معالج ثابت = () => { console.log("handler"); // سيتم تمرير وظيفة المعالج هنا إلى المكون الفرعي}; console.log("تقديم الوالدين"); يعود ( <div className="App"> <button onClick={handleClick}>الزيادة</button> <h2>{العد</h2> <اسم الطفل={"جو"} ChildFunc={handler} /> </div> ); }
سيظل رمز المكون الفرعي كما هو. لن نستخدم الوظائف التي تم تمريرها من المكونات الأصلية في المكونات الفرعية:
//Child.js وظيفة التصدير الطفل (الدعائم) { console.log("عرض الطفل"); يعود ( <ديف> <h2>{props.name}</h2> </div> ); } Export default React.memo(Child);
الآن عندما نقوم بزيادة count
في المكون الأصلي، فإنه يعيد عرض المكون الفرعي ويعيد عرضه في نفس الوقت على الرغم من عدم وجود تغيير في المعلمات التي تم تمريرها.
إذًا، ما الذي يسبب إعادة عرض المكون الفرعي؟ الجواب هو أنه في كل مرة يتم فيها إعادة عرض المكون الأصلي، يتم إنشاء وظيفة handler
جديدة وتمريرها إلى المكون الفرعي. الآن، نظرًا لإعادة إنشاء وظيفة handle
في كل عملية إعادة تصيير، سيجد المكون الفرعي أن مرجع handler
قد تغير عند إجراء مقارنة سطحية للدعائم، ويعيد تصيير المكون الفرعي.
بعد ذلك، سنغطي كيفية حل هذه المشكلة.
useCallback()
لتجنب المزيد من عمليات إعادة العرض.المشكلة الرئيسية في إعادة عرض المكون الفرعي هي إعادة إنشاء وظيفة handler
، مما يؤدي إلى تغيير المرجع الذي تم تمريره إلى المكون الفرعي. ولذلك، نحن بحاجة إلى وسيلة لتجنب هذه الازدواجية. إذا لم يتم إعادة إنشاء وظيفة handler
، فلن يتغير المرجع إلى وظيفة handler
، لذلك لا يتم إعادة عرض المكون الفرعي.
لتجنب الاضطرار إلى إعادة إنشاء الوظيفة في كل مرة يتم فيها تصيير المكون الأصلي، سنستخدم خطاف React يسمى useCallback(). تم تقديم الخطافات في React 16. لمعرفة المزيد حول الخطافات، يمكنك مراجعة وثائق الخطافات الرسمية الخاصة بـ React، أو التحقق من "خطافات React: كيفية البدء وإنشاء خطاف خاص بك".
يأخذ الخطاف useCallback()
معلمتين: وظيفة رد الاتصال وقائمة التبعيات
ما يلي هو مثال على useCallback()
:
const HandleClick = useCallback(() => {
.
// افعل شيئا }, [x,y]);
هنا، تتم إضافة useCallback()
إلى الدالة handleClick()
. يمكن أن تكون المعلمة الثانية [x, y]
عبارة عن مصفوفة فارغة، أو تبعية واحدة، أو قائمة من التبعيات. يتم إعادة إنشاء الدالة handleClick()
فقط عند تغيير أي من التبعيات المذكورة في المعلمة الثانية.
إذا لم تتغير التبعيات المذكورة في useCallback()
، فسيتم إرجاع الإصدار المحفوظ من وظيفة رد الاتصال المذكورة كوسيطة أولى. سنقوم بتغيير المكون الأصلي لاستخدام الخطاف useCallback()
على المعالجات التي تم تمريرها إلى المكون الفرعي:
//Parent.js تصدير الوظيفة الافتراضية الأصل () { const [count, setCount] = useState(0); مقبض ثابت انقر = () => { setCount(count + 1); }; const Handler = useCallback(() => { // استخدم useCallback() لوظيفة المعالج console.log("المعالج"); }, []); console.log("تقديم الوالدين"); يعود ( <div className="App"> <button onClick={handleClick}>الزيادة</button> <h2>{العد</h2> <اسم الطفل={"جو"} ChildFunc={handler} /> </div> ); }
سيظل رمز المكون الفرعي كما هو.
الكود الكامل لهذا المثال موجود في وضع الحماية هذا.
عندما نقوم بزيادة count
في المكون الأصلي للكود أعلاه، يمكننا أن نرى الإخراج التالي:
عرض الأصل تقديم الطفل تقديم الوالدين تقديم الوالدين تقديم الأصل
نظرًا لأننا نستخدم خطاف useCallback()
handler
في المكون الأصلي، فلن تتم إعادة إنشاء وظيفة handler
في كل مرة يتم فيها إعادة عرض المكون الأصلي، وسيتم تمرير إصدار التذكير الخاص handler
إلى المكون الفرعي. سيقوم المكون الفرعي بإجراء مقارنة سطحية ويلاحظ أن المرجع إلى وظيفة handler
لم يتغير، لذلك لن يستدعي طريقة render
.
الحفظ وسيلة جيدة لتجنب إعادة العرض غير الضرورية للمكونات عندما لا تتغير حالتها أو خصائصها، وبالتالي تحسين أداء تطبيقات React. قد تفكر في إضافة ميزة الحفظ إلى جميع مكوناتك، لكن هذه ليست بالضرورة الطريقة لبناء مكونات React عالية الأداء. يجب استخدام الحفظ فقط إذا كان المكون:
في هذا البرنامج التعليمي فهمنا:
React.memo()
لمكونات الوظيفة و React.PureComponent
لمكونات الفئةReact.memo()
useCallback()
مشاكل إعادة العرض عندما يتم تمرير الوظائف كدعائم إلى المكونات الفرعية،آمل أن تكون هذه المقدمة إلى React Memoization مفيدة لك!
العنوان الأصلي: https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/
المؤلف الأصلي: نداء خان