Reactالدالة useMemo()
- مقدمة للدالة
useMemo()
في React - تحضير مشروع للتطبيق العملي
- مشكلة إعادة الحصول على القيم في React
- تخزين القيم في الذاكرة لتحسين الأداء في React
- تحديد متى سيتم تحديث القيم المخزنة في الذاكرة في React
مقدمة للدالة useMemo()
في React
أي تحديث يتم في واجهة المستخدم يؤدي إلى إعادة رسم محتواها من جديد حتى تظهر التحديثات الجديدة فيها. في بعض الحالات قد يسبب هذا الأمر بطئ في الأداء حيث أن إعادة رسم واجهة المستخدم تعني إعادة تنفيذ الأوامر الموضوعة في المكونات من جديد و التي قد يتطلب تنفيذها بعض الوقت مما يؤدي إلى حصول بطئ أو تجميد مؤقت في واجهة المستخدم.
في حال أردت الإحتفاظ بالقيم التي يتطلب الحصول عليها بعض الوقت لأجل عرضها نفسها في كل مرة يتم فيها رسم واجهة المستخدم فإنه يمكن وضع الأوامر التي تحضر هذه القيم بداخل الدالة useMemo()
و هكذا لن يتم إعادة تنفيذ هذه الأوامر من جديد كلما تم إعادة رسم واجهة المستخدم.
هذه الدالة هي من ضمن دوال الهووكس ( Hooks ) التي يمكن استخدامها في المكوّن المبني بأسلوب Function Component.
الدالة useMemo()
تشبه الدالة useCallback()
من حيث الفكرة و طريقة الإستخدام و باختصار شديد فإن الفرق بينهما هو ما يلي:
useMemo()
_ تحفظ القيمة التي ترجعها الدالة لأجل إعادة استعمالها عند رسم المكوّن.useCallback()
_ تحفظ التغييرات التي حصلت عند تنفيذ الدالة لأجل إعادة فعلها نفسها عند رسم المكوّن.
طريقة تضمينها
حتى تتمكن من استخدام الدالة useMemo()
يجب تضمينها أولاً كما يلي.
مثال
import { useMemo } from "react";
الشكل العام لاستخدامها
عند استخدام هذه الدالة فإنه يتم ربطها بالشيء الذي سيتم مراقبة قيمته و بالدالة التي من خلالها سيتم تحديث قيمته.
مثال
const cachedValue = useMemo(calculateValue, dependencies)
calculateValue
_ هي القيمة التي سترجع في النهاية من الدالة التي تحتوي على أوامر قد يتطلب تنفيذها بعض الوقت و التي لا نريد إعادة تنفيذها كلما تم رسم واجهة المستخدم.dependencies
_ هو باراميتر إختياري مكانه يمكن تمرير مصفوفة بأسماء المتغيرات المراد إعادة تنفيذ الدالة إذا تغيرت قيمها.cachedValue
_ القيمة التي سيتم إرجاعها من الدالةcalculateValue
سيتم تخزينها فيه مما يسمح لنا بمشاركة نتيجتها مع المكونات الداخلية.
ليس عليك التفكير باستخدام الدالة useMemo()
لحظة بناء واجهة المستخدم، بل هذا الأمر تفعله لاحقاً حين تكون بحاجة لتحسين الأداء و يكون هناك إمكانية للإستفادة منها. و حقيقةً هذه من الدوال التي تستخدمها في حالات قليلة.
تحضير مشروع للتطبيق العملي
إفعل الخطوات التالية تباعاً حتى تنشئ مشروع جديد و تطبق فيه الأمثلة كما فعلنا بالضبط:
- قم بإنشاء مشروع جديد إسمه
demo-app
. - بداخل المجلد
src
تجد ملف إسمهApp.js
قم بحذف كل الكود الإفتراضي الموجود فيه.
في هذا المشروع سنقوم فقط بالتعامل مع المكوّن <App>
.
مشكلة إعادة الحصول على القيم في React
في المثال التالي، قمنا بتعريف دالة إسمها expensiveCalculation()
عند استدعائها تنفذ حلقة تكرر الأوامر الموضوعة فيها مليار مرة و في النهاية ترجع العدد مليار.
في المكوّن <App>
فعلنا ما يلي:
- قمنا باستدعاء الدالة
expensiveCalculation()
و تخزين القيمة التي سترجعها في متغير إسمهbigNumber
. - قمنا بتعريف متغير إسمه
counter
يمكن تحديث قيمته بواسطة دالة إسمهاsetCounter()
. - قمنا بتعريف دالة إسمها
increaseCounter()
مهمتها زيادة قيمة المتغيرcounter
واحداً كلما تم النقر على زر موضوع في المكوّن.
عند تجربة هذا المثال فإنه كلما تم النقر على الزر الموضوع في الصفحة فإنه يفترض أن يتم إعادة رسم المكوّن <App>
مما يعني إعادة تنفيذ الدالة expensiveCalculation()
من جديد و التي قد يستغرق تنفيذها حوالي ثانتين أو أكثر لأننا وضعنا فيها حلقة تعيد تكرار الكود الموجود فيها مليار دورة.
مثال
// useState هنا قمنا بتضمين الدالة import { useState } from 'react'; // مهمتها إرجاع العدد مليار عندما يتم استدعاؤها expensiveCalculation هنا قمنا بتعريف دالة إسمها function expensiveCalculation() { let num = 0; for (let i = 0; i < 1000000000; i++) { num += 1; } return num; }; // الذي يمثل الحاوية الرئيسية في واجهة المستخدم <App> هنا قمنا بتعريف المكون function App() { // bigNumber و تخزين العدد الذي ترجعه في متغير إسمه expensiveCalculation() هنا قمنا باستدعاء الدالة const bigNumber = expensiveCalculation(); // setCounter() قيمته الأولية 0 و يمكن تحديث قيمته من خلال استدعاء دالة إسمها counter هنا قمنا بتعريف متغير إسمه const [counter, setCounter] = useState(0); // فقط counter هنا قمنا بتعريف الدالة التي سيتم تنفيذها عند النقر على الزر و التي مهمتها زيادة واحد على قيمة المتغير const increaseCounter = () => { setCounter(value => value + 1); }; // في الصفحة <App> هنا قمنا بتحضير ما سيتم عرضه عند تضمين المكوّن return ( <> <button onClick={increaseCounter}>Increase Counter</button> <p>counter = {counter}</p> <hr/> <p>bigNumber is calculated once and has a value of {bigNumber}</p> </> ); } // يمكن الوصول إليه من خارج هذا الملف لأنه المكون <App> هنا قمنا بجعل المكون // index.html الرئيسي في الصفحة و الذي يتم تضمينه بشكل تلقائي في الملف export default App;
أثناء تجربة المثال السابق ستلاحظ حصول تعليق بسيط كلما تم النقر على الزر الموضوع في الصفحة و لكننا سنعلمك كيف تحل هذه المشكلة في المثال التالي.
تخزين القيم في الذاكرة لتحسين الأداء في React
فيما يلي قمنا بإعادة المثال السابق مع إجراء تعديل بسيط عليه، حيث أننا قمنا هذه المرة بتخزين القيمة التي ترجعها الدالة expensiveCalculation()
في الدالة useMemo()
و التي قمنا بوضعها في النهاية في المتغير bigNumber
.
بهذا التعديل البسيط قمنا بتحسين الأداء بشكل كبير لأنه في كل مرة يتم فيها رسم المكوّن لن يتم إعادة تنفيذ الدالة expensiveCalculation()
التي يستغرق تنفيذها بعض الوقت من أجل الحصول على الناتج الذي ترجعه لنا بل سيتم استعمال الناتج الذي سبق و تم تخزينه في الذاكرة بواسطة الدالة useMemo()
.
مثال
// useMemo و useState هنا قمنا بتضمين الدالتين import { useState, useMemo } from 'react'; // مهمتها إرجاع العدد مليار عندما يتم استدعاؤها expensiveCalculation هنا قمنا بتعريف دالة إسمها function expensiveCalculation() { let num = 0; for (let i = 0; i < 1000000000; i++) { num += 1; } return num; }; // الذي يمثل الحاوية الرئيسية في واجهة المستخدم <App> هنا قمنا بتعريف المكون function App() { // حتى يتم تخزين القيمة التي ترجعها في الذاكرة useMemo() في الدالة expensiveCalculation() هنا قمنا بتمرير الدالة // bigNumber و المخزنة في الأصل في الذاكرة في متغير إسمه useMemo() بعدها وضعنا القيمة التي ترجعها الدالة const bigNumber = useMemo(()=> expensiveCalculation(), []); // setCounter() قيمته الأولية 0 و يمكن تحديث قيمته من خلال استدعاء دالة إسمها counter هنا قمنا بتعريف متغير إسمه const [counter, setCounter] = useState(0); // فقط counter هنا قمنا بتعريف الدالة التي سيتم تنفيذها عند النقر على الزر و التي مهمتها زيادة واحد على قيمة المتغير const increaseCounter = () => { setCounter(value => value + 1); }; // في الصفحة <App> هنا قمنا بتحضير ما سيتم عرضه عند تضمين المكوّن return ( <> <button onClick={increaseCounter}>Increase Counter</button> <p>counter = {counter}</p> <hr/> <p>bigNumber is calculated once and has a value of {bigNumber}</p> </> ); } // يمكن الوصول إليه من خارج هذا الملف لأنه المكون <App> هنا قمنا بجعل المكون // index.html الرئيسي في الصفحة و الذي يتم تضمينه بشكل تلقائي في الملف export default App;
نلاحظ أن الأداء أصبح سريع جداً و سبب ذلك أن القيمة التي أرجعتها الدالة expensiveCalculation()
بعد أن تم تنفيذها أول مرة تم تخزينها في الذاكرة، و أصبح يتم الحصول على القيمة بشكل مباشر من الذاكرة في كل مرة يتم فيها رسم المكون <App>
من جديد.
تحديد متى سيتم تحديث القيم المخزنة في الذاكرة في React
فيما يلي قمنا بإعادة المثال السابق مع إجراء تعديل التعديلات التالية عليه:
- أضفنا باراميتر إسمه
num
للدالةexpensiveCalculation()
و أصبحت ترجع ناتج جميع مليار بالإضافة إليه قيمته. - أضفنا متغير جديد إسمه
amount
و قيمته الأولية 0. - عند استدعاء الدالة
expensiveCalculation()
قمنا بتمرير قيمة المتغيرamount
لها. - في المصفوفة الخاصة بالدالة
useMemo()
قمنا بتمرير المتغيرamount
لجعلها تحدّث القيمة التي تخزّنها في الذاكرة في حال تغيّرت قيمته. - أضفنا زر جديد خاص لتحديث قيمة المتغير
amount
حيث سيتم إضافة واحد على قيمته في كل نقرة.
إذاً الصفحة أصبح فيها زرّين، الأول يضيف واحد فقط على قيمة المتغير counter
أما الثاني فيضيف واحد على قيمة المتغير amount
و بشكل تلقائي سيتم إعادة تنفيذ الدالة expensiveCalculation()
لأن القيمة التي تخزنها في الذاكرة مرتبطة بقيمته.
مثال
// useMemo و useState هنا قمنا بتضمين الدالتين import { useState, useMemo } from 'react'; // مضافاً إليها مليار حينما يتم استدعاؤها num مهمتها إرجاع قيمة expensiveCalculation هنا قمنا بتعريف دالة إسمها function expensiveCalculation(num) { for (let i = 0; i < 1000000000; i++) { num += 1; } return num; }; // الذي يمثل الحاوية الرئيسية في واجهة المستخدم <App> هنا قمنا بتعريف المكون function App() { // setAmount() قيمته الأولية 0 و يمكن تحديث قيمته من خلال استدعاء دالة إسمها amount هنا قمنا بتعريف متغير إسمه const [amount, setAmount] = useState(0); // حتى يتم تخزين القيمة التي ترجعها في الذاكرة useMemo() في الدالة expensiveCalculation() هنا قمنا بتمرير الدالة // bigNumber و المخزنة في الأصل في الذاكرة في متغير إسمه useMemo() بعدها وضعنا القيمة التي ترجعها الدالة const bigNumber = useMemo(()=> expensiveCalculation(amount), [amount]); // setCounter() قيمته الأولية 0 و يمكن تحديث قيمته من خلال استدعاء دالة إسمها counter هنا قمنا بتعريف متغير إسمه const [counter, setCounter] = useState(0); // فقط counter هنا قمنا بتعريف الدالة التي سيتم تنفيذها عند النقر على الزر و التي مهمتها زيادة واحد على قيمة المتغير const increaseCounter = () => { setCounter(value => value + 1); }; // فقط amount هنا قمنا بتعريف الدالة التي سيتم تنفيذها عند النقر على الزر و التي مهمتها زيادة واحد على قيمة المتغير const increaseAmount = () => { setAmount(value => value + 1); }; // في الصفحة <App> هنا قمنا بتحضير ما سيتم عرضه عند تضمين المكوّن return ( <> <button onClick={increaseCounter}>Increase Counter</button> <p>counter = {counter}</p> <button onClick={increaseAmount}>Increase BigNumber</button> <p>bigNumber = {bigNumber}</p> </> ); } // يمكن الوصول إليه من خارج هذا الملف لأنه المكون <App> هنا قمنا بجعل المكون // index.html الرئيسي في الصفحة و الذي يتم تضمينه بشكل تلقائي في الملف export default App;
في المثال السابق لو لم نستخدم الدالة useMemo()
لكان سيحدث تعليق عند النقر على أي زر موضوع في الصفحة لأن الدالة expensiveCalculation()
كانت ستتنفذ كلما تم إعادة رسم المكوّن <App>
سواء تغيّرت قيمة المتغير amount
أم لم تتغير.