کتابخانه React برای بهینه سازی عملکرد برنامهها، دو hook داخلی در اختیار ما قرار میدهد:useMemo و useCallback. در نگاه اول ممکن است به نظر برسد که کاربرد آنها کاملا مشابه است، اما موارد استفاده از هرکدام متفاوت است. برای از بین بردن این سردرگمی، بیایید با هم آنها را بررسی کنیم و تفاوت واقعی و روش صحیح استفاده از هر دو را بفهمیم.
کامپوننتهای فانکشنال دارای مشکل اند
کامپوننتهای فانکشنال (تابعی) عالی هستند. ترکیب آنها با hook ها امکان استفاده مجدد و انعطاف پذیری کد بسیار بیشتری نسبت به اجزای کلاس را فراهم میکند. با این حال آنها یک مشکل دارند: یک کامپوننت فانکشنال همان تابع رندری است که قبلا در کامپوننتهای کلاس داشتیم. این تابعی است که با تغییر هر prop / state در حال اجرا است.
این بدان معناست که:
- اگر یک تابع درون کامپوننت فراخوانی شود، بارها و بارها در هر رندر مجدد ارزیابی میشود.
- اگر یک تابع در داخل کامپوننت ایجاد شود، به یک مولفه child منتقل میشود و دوباره ایجاد میشود، به این معنی که نشانگر تغییر میکند و باعث میشود child مجددا رندر کند یا تابع را بدون نیاز فراخوانی کند (بسته به شرایط موجود).
برای حل این مشکل و جلوگیری از بروز مشکلات احتمالی در تابع، ریاکت دو hook برای ما فراهم میکند: useMemo و useCallback.
useMemo
بیایید با اولین مسئله شروع کنیم و ببینیم چگونه میتوانیم از ارزیابی غیرضروری توابع جلوگیری کنیم.
در نسخه نمایشی زیر، ما یک component با دو state داریم: یکی شماره را ذخیره میکند و دیگری بولی است.
ما باید بر روی عددی که در state خود داریم محاسباتی را انجام دهیم، بنابراین تابع plusFive خود را فراخوانی میکنیم و نتیجه را ارائه میدهیم.
const plusFive = (num) => {
console.log("I was called!");
return num + 5;
};
export default function App() {
const [num, setNum] = useState(0);
const [light, setLight] = useState(true);
const numPlusFive = plusFive(num);
return (
...
دمو را از این لینک مشاهده کنید.
اگر کنسول را باز کنید، خواهید دید که plusFive فراخوانی میشود، چه روی "به روز رسانی شماره" که شماره جدیدی را تنظیم میکند کلیک کنیم و چه "روشن کردن نور" که حالت بولی را به روز میکند (هیچ ارتباطی با numPlusFive ندارد) کلیک کنیم.
بنابراین چگونه میتوانیم از بروز این اتفاق جلوگیری کنیم؟ با به خاطر سپردن plusFive!
تا زمانی که شماره جدیدی دریافت نکنیم، تابع فراخوانی نمیشود. محاسبه انجام نمیشود و بلافاصله نتیجه را دریافت خواهیم کرد.
ما این کار را با استفاده از useMemo انجام خواهیم داد که به صورت زیر خواهد بود:
const numPlusFive = useMemo(() => plusFive(num), [num]);
useMemo دو پارامتر دریافت میکند: تابعی که فراخوانی تابع ما را برمیگرداند و آرایهای از وابستگیها. فقط با تغییر یکی از وابستگیها، تابع ما دوباره فراخوانی میشود. useMemo نتایج حاصل از اجرای تابع را برمیگرداند و آن را در حافظه ذخیره میکند تا در صورت استفاده از پارامترهای مشابه، از اجرای مجدد تابع جلوگیری کند.
پیش بروید و خودتان ببینید (باز کردن کنسول را فراموش نکنید).
useCallback
اکنون که میدانیم چگونه میتوان از ارزیابی مجدد توابع جلوگیری کرد، بیایید ببینیم چگونه میتوان از ایجاد مجدد توابع ایجاد شده در داخل کامپوننتها در هر رندر نیز جلوگیری کرد.
در نسخه ی نمایشی زیر، ما یک کامپوننت child (<SomeComp>) داریم و تابعی که در داخل کامپوننت اصلی ایجاد کردهایم (<App>) را به عنوان prop دریافت میکند. توجه داشته باشید که این تابع در داخل قلاب useEffect استفاده میشود و از آنجا که در لیست وابستگی useEffect ذکر شده است، مجددا فراخوانی میشود. حتی وقتی state های دیگری را تغییر میدهیم یا prop ها را دریافت میکنیم، مربوط به تابع "plusFive" ما نیست.
const App = () => {
const [num, setNum] = useState(0);
const [light, setLight] = useState(true);
const plusFive = () => {
console.log("I was called!");
return num + 5;
};
return (
<div className={light ? "light" : "dark"}>
<div>
<SomeComp someFunc={plusFive} />
<button onClick={() => { setLight(!light); }}> Toggle the light </button>
</div>
</div>
);
}
const SomeComp = ({ someFunc }) => {
const [calcNum, setCalcNum] = useState(0);
useEffect(() => {
// In this scenatio, someFunc will change on every render, so this useEffect will run.
setCalcNum(someFunc());
}, [someFunc]);
return <span> Plus five: {calcNum}</span>;
};
برای مشاهده دمو روی این لینک کلیک کنید.
برای جلوگیری از ایجاد مجدد تابع خود و تغییر نشانگرها در هر دور رندر، میتوانیم از useCallback استفاده کنیم. این hook در ریاکت دو پارامتر دریافت میکند: یک تابع و یک آرایه از وابستگیها:
const plusFive = useCallback(() => {
console.log("I was called!");
return num + 5;
}, [num]);
این hook به ما این امکان را میدهد که تابع را حفظ کنیم و فقط در صورت تغییر یکی از وابستگیها، دوباره ایجاد میشود.
این دمو را میتوانید ببینید.
از این hook ها سوء استفاده نکنید
در حالی که این دو hook راهحلی برای یک مشکل واقعی ارائه میدهند، اما ممکن است خیلی راحت مورد سوء استفاده قرار بگیرند و حتی صدمات بیشتری وارد کنند.
به عنوان مثال، نیازی به یادآوری تابعی نیست که برخی محاسبات اساسی را انجام میدهد (مانند دمو). هر زمان که میخواهید از اجرای مجدد توابع، زمان طولانی یا استفاده از منابع زیاد جلوگیری کنید، از useMemo استفاده کنید. چرا؟ زیرا useMemo نتایج اجرای توابع را در حافظه نگه میدارد و ممکن است به طور بالقوه رشد کند و از قضا به تابع برنامه شما آسیب برساند.
با useCallback ممکن است همه چیز بدتر شود. در صورت عدم استفاده از useCallback، تابع قدیمی شما به عنوان زباله جمع میشود، اما با useCallback در حافظه باقی میماند. در صورتی که یکی از وابستگیها مجددا اجرا شود تا آن نسخه تابع قدیمی بازگردد.
بنابراین چه زمانی استفاده از useCallback درست است؟ وقتی واقعا میبینید که استفاده نکردن از آن به تابع شما آسیب میرساند یا منجر به اجرای تابعی ناخوشایند میشود (در نسخه دمو useCallback تصور کنید که این تابع یک فراخوانی API را انجام میدهد و نه فقط افزودن شماره. این چیزی است که قابل جلوگیری است).
برای خلاصه:
- useMemo اگر مجموعهای از پارامترهایی را که قبلا استفاده شده بود را دریافت نکرده باشد، مانع اجرای مجدد تابع می شود و نتایج یک تابع را برمی گرداند. هنگامی که میخواهید از انجام عملیات سنگین یا پرهزینه در هر رندر جلوگیری کنید، از آن استفاده کنید.
- useCallback بر اساس لیستی از وابستگیها، از ایجاد مجدد یک تابع جلوگیری میکند. این خود تابع را برمیگرداند. وقتی میخواهید آن را به کامپوننتهای child منتقل کنید، از آن استفاده کنید و از اجرای مجدد توابع پرهزینه جلوگیری کنید.
امیدواریم این مقاله برایتان مفید واقع شود. اگر هرگونه سوال یا نظری دارید، در بخش زیر با ما در میان بگذارید، خوشحال میشویم به آنها پاسخ دهیم.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید