با نسخه 16.8، تعدادی هوک کاربردی اضافه شد که میتونی در برنامه های ریاکت ازشون استفاده کنی. یکی از این هوک ها useMemo بود. این هوک سرعت اجرای برنامه رو بهبود میده.
در این مقاله میبینیم که رندر دوباره re-render چجوری کار میکنه، چرا مهمه که بدونی، و اینکه useMemo چجوری سرعت اجرای این عملکرد رو بهبود میده. همچنین یاد میگیری که useMemo چه مشکلاتی میتونه داشته باشه.
هوک useMemo دو تا مشکل رو حل میکنه:
در چرخه حیات کامپوننت، وقتی بروزرسانی رخ میده ریاکت کامپوننت رو دوباره رندر میکنه، به نجوی که جاوااسکریپت مقایسات برابری و سطحی shallow رو انجام میده ممکنه تغییری ناخواسته یا غیرمنتظره رو تشخیص بده. این تغییر سبب میشه که کامپوننت بدون دلیل دوباره رندر بشه.
علاوه بر این، اگه رندر دوباره عملیات سنگینی باشه، مثلا حلقه for loop طولانی، به سرعت اجرا لطمه میزنه. عملیات سنگین میتونه برای زمان، حافظه، یا پردازش هزینهبر باشه. علاوه بر این مشکلات فنی به تجربه کاربری هم صدمه میزنه.
اگه کامپوننت دوباره رندر بشه، تمام زیردرخت کامپوننت دوباره رندر میشه.
از اینرو، ریاکت ایده useMemo رو ارائه داد تا این مشکل رو حل کنه.
مفهوم Memoization یا بمعنای کلمه به خاطر سپاری تکنیکیست که تابعی پیچیده رو بخاطر میسپاره. در این روش، وقتی پارامترهای یکسان متعاقبا به تابع داده میشه نتیجه به خاطر سپرده میشه.
اگه تابعی داشته باشیم که 1+1 رو محاسبه کنه، نتیجه 2 رو میده. ولی اگه از Memoization استفاده کنیم، دفعه بعدی که همین اعداد رو به تابع بدیم، اونارو با هم جمع نمیکنه؛ و بدون اینکه محاسبه ای انجام بده جواب 2 رو به یاد میاره.
نوشتار useMemo به این شکله:
const memoizedValue = React.useMemo(() => computeExpensiveValue(a, b), [a, b]);
هوک useMemo یک تابع و آرایه ای از تعلقات dependencies میگیره.
این آرایه مشابه آرگومان های تابع کار میکنه. هوک useMemo این لیست رو تحت نظر داره: اگه تغییری نباشه، نتیجه تابع یکسان میمونه. درغیراینصورت، تابع رو دوباره اجرا میکنه. اگه لیست تغییری نداشته باشه، مهم نیست اگه کامپوننت دوباره رندر بشه، تابع دوباره اجرا نمیشه و در عوض نتیجه ذخیره شده رو برمیگردونه. اگه تابعی که استفاده میشه بزرگ و سنگین باشه این شرایط سرعت اجرا رو بهینه سازی میکنه.
این مثالی انتزاعی از هوک useMemo برای آرایه ای از آیتم هاست که از دو تابع محاسباتی سنگین استفاده میکنه:
const List = React.useMemo(() => listOfItems.map(item => ({ ...item, itemProp1: expensiveFunction(props.first), itemProp2: anotherPriceyFunction(props.second) })), [listOfItems] )
در مثال فوق، تابع useMemo در رندر اول اجرا میشه.
از اینرو، تا زمانیکه توابع سنگین تموم بشن thread مسدود میشه.
با این حال، در رندرهای بعدی، تا زمانیکه listOfItems تغییری نکرده نیازی نیست که توابع سنگین دوباره اجرا بشن. هوک useMemo نتیجه هر تابع رو به خاطر میسپاره.
بگونه ای هست که انگار این توابع بصورت آنی رندر میشن. اگه توابعی همزمان synchronous و سنگین داری بهتره که از این روش استفاده کنی.
اول کدت رو بنویس بعد برگرد ببین میتونی بهینه کنی یا نه. اگه از useMemo مکررا استفاده کنی، ممکنه به سرعت اجرا لطمه بزنه.
وقتی میخوای از useMemo استفاده کنی، میتونی از ابزارهای پروفایلینگ برای تشخیص مشکلات اجرای سنگین کمک بگیری. سنگین یعنی برنامه منابع (مثل حافظه) زیادی استفاده میکنه.
علاوه بر useMemo، هوک های useCallback، useRef و useEffect رو هم داریم.
هوک useCallback مشابه useMemo هست، با این تفاوت که تابع memoized برمیگردونه، درحالیکه useMemo تابعیه که مقداری رو برمیگردونه.
برای درخواست های async نباید از useMemo استفاده بشه. در این موارد از useEffect استفاده کن.