تو بحث performance برنامه های react ای نکاتی که مدنظر باید قرار بدیم اینه که بدونیم چه انتظاری باید از سرعت برنامه داشته باشیم مثلا load اولیه چقدر باید طول بکشه یا سرعت interaction با input ها چطوره و از طرفی هم با ابزارهای موجود باید performance اندازه گیری بشه تا بدونیم مشکلات و گلوگاه ها کجاست که بتونیم برطرفش کنیم اول میریم سراغ انتظارات:
· سرعت لود اولیه صفحه بین 0.5 تا 2 ثانیس
· سرعت پاسخ به input بین 50 تا 200 میلی ثانیه
· هر کامپوننتی که بیشتر از یه ثانیه طول میکشه نیاز به نمایش loader داره
· در بیشتر browser ها هر ثانیه 60 بار re-render خواهد شد
· در مورد انیمیشن ها و transition ها 10 فریم بر ثانیس
اما کی به اینا توجه میکنه و براش مهمه؟ چرا کسی دقتی تو این سرعتا نداره؟ چون اکثر توسعه دهنده و طراح ها از سیتم های قوی و پهنای باند شبکه بالا بهره میبند درحالیکه کاربر نهایی عمدتا از گوشی های موبایل ارزان قیمت با سخت افزار ضعیف استفاده می کنند و خیلی مکان ها هم سرعت اینترنت بالایی ندارند بنابراین اونجا این سرعتا خودشون نشون میده و گاها باعث کلافگی و نهایت بستن اپ و خداحافظی کردن میشه.
خوب همه میگن react سریعه و سرعت بالایی داره ولی از طرفی reconciliation هزینه بر است و اگر بدرستی مدیریت نشود این سرعت react به چشم نمیاد.
و اما reconciliation چی هست؟
React تغییر مستقیم در DOM را از طریق virtual DOM محدود میکنه بنابراین هر تغییر شما یک reconciliation را فعال می کنه بطوریکه DOM واقعی و virtual یکسان می شن بنابراین هر re render کردنی باعث ایجاد فرآیند reconciliation میشه و لازمه تا حد امکان ازش اجتناب کنیم.
حالا virtual DOM چیه؟ در واقع یک object ایه که react ازش برای نمایش عناصر در صفحات استفاده می کنه و مهمترین کاربردش برای شناسایی المان های تغییر کرده در صفحه است تا فقط همونا re render شن و این دقیقا جاییه که باعث میشه react سریع باشه.
DOM ساختار درختی داره و هر نودی که تغییر میکنه منجر به re render شدن کل کامپوننت های خودش و زیرمجموعه هاش میشه درحالیکه ممکنه هیچکدوم از بچه هاش تغییری نکرده باشند و نیازی به re render نباشه. فرض کنیم یک جدول به صورت لیستی از اطلاعات داریم و بالای صفحه یک دکمه داریم که رنگ theme پس زمینه رو عوض میکنه و با توجه به اینکه جدول زیرمجموعه اون صفحه هست و تغییر رنگ در بالاترین سطح اتفاق میافته پس جدول هم درحالیکه داده ها هیچ تغییری نداشته re render میشه ولی آیا واقعا لازمه؟
برای شناسایی کامپوننت هایی که تغییر کرده تو صفحه درحالیکه بروزرسانی نداشته و هزینه بر است از یک کتابخانه با نام why-did-you-update میشه استفاده کرد خیلی هم راحته کافیه نصب کنیم و بعد از import در صفحه مربوطه whyDidYouUpdate(React) رو صدا بزنید و لاگ مربوطه رو در console ببینید.
ابزار دیگه برای اندازگیری سرعت react app ابزاری به نام react Profiler عه سرعت load رو نشان میده و همچنین نحوه update شدن و فازهای commit. در اینجا فرآیند reconciliation رو میتونیم ببینیم چقدر زمان میبره و یه تصویری از اینکه هر کامپوننت چقدر طول میکشه load بشه به ترتیب slow به fast بما میده. در فاز کامیت react المنت ها را به DOM اضافه، حذف و یا بروزرسانی میکنه . در browser میتونید روی تب profiler کلیک کنید بعد از اون لازمه عملیات رکورد رو شروع کنید بعد از اون یه interaction با صفحه داشته باشید مثلا همون عوض کردن تم و در نهایت رکورد رو متوقف کنید اطلاعات زمانی کامیت و رندر شدن رو نمایش میده و میتونین جاهایی که لازم نیست و زمان بره شناسایی بشه.
حالا راه هایی که باعث افزایش سرعت میشه چیاس؟
تو lifecycle هر کامپوننت متدی به نام shouldComponentUpdate وجود داره که بصورت پیش فرض true هست حالا میشه برای جلوگیری از re render شدن در برخی مواقع و با بررسی چک های لازم این مقدار را false برگرداند تا خودش و بچه هاش re render نشن.
راه بعدی استفاده از pure component هاست بجایclass component های رایج که در این حالت نیازی به مدیریت shouldComponentUpdate نداریم در این نوع کامپوننت ها اگر update ای داشته باشیم re rendr اتفاق میافته اگر نباشه نمیافته.
هشدارهایی در خصوص استفاده از pure component ها:
· فقط در class component ها که بصورت stateful هستن قابل استفاده است
· shouldComponentUpdate را بصورت shallow comparison پیاده سازی میکند (این مقایسه در واقع deep نیست یعنی دو object را اگر دارای relation باشند را بررسی نمیکند)
· در استفاده از this.props.children باید دقت کرد
حالا اگه بخوایم از قابلیت بالا تو functional component ها استفاده کنی که در واقع از re render شدن بیخودی جلوگیری بشه میتونیم از خاصیت React.memo() استفاده کنیم در واقع یجور قرار دادن اون component در memory عه که cache میشه برای استفاده مجدد و re render نشدن اضافی
نکاتی در خصوص استفاده از React.memo:
با توجه به اینکه memo هم برا فهمیدن تغییرات از shallow comparison استفاده میکنه و البته قابل اعتماد نیس میتونه به عنوان پارامتر دوم memo یه تابع که عملیات مقایسه رو انجام میده ارسال بشه مثل
Function areEqual(prevProps, nextProps){
If(prevProps === nextProps){
Return true
} else return false
}
React.memo(functional component,areEqual)
عملیات بالا رو میتونید با profiler دوباره اندازه گیری کنید تا تفاوت سرعت در load و commit رو ببینید.
استفاده از hook ها بدلیل نداشتن متدهای اضافی lifecycle در هنگام build بصورت minify خواهند بود که این موضوع باعث سبک تر بودن میشه و از این لحاظ به استفاده از class component ها ترجیح داده میشه
حالا برای بهبود performance میتونیم از hook های مربوط به memo , callback استفاده کنیم.
Const sampleMemo = React.useMemo( () => functionValue(), list);
کد بالا در واقع نتیجه اجرای functionValue رو تا زمانیکه list تغییر نکنه memorize میکنه و از re run شدن جلوگیری میکنه.
در کنار useMemo hook میتونیم برای افزایش performance از useCallback hook هم استفاده کرد با این تفاوت که useMemo یک value که نتیجه اجرای یک function است را برمیگرداند ولی usecallback یک function را که قرار است بعدا call شود را برمیگرداند.
نکته مهم بعدی در خصوص بحث performance استفاده از list های طولانی و modification در عناصر یک آرایه است. مهمترین نکته ای که باید رعایت بشه اینه که هر المنت موجود در لیست باید key داشته باشه چرا که هنگام تغییر تو هریک از المنت ها برای پیدا کردن و update اون از این key که بایستی منحصربفرد باشد استفاده می شه. اشتباه رایجی که اکثرا مرتکب میشن استفاده از index برای key است که این موضوع در زمان تغییرات عمده و حذف از ارایه باعث میشه index ها re order بشن و باعث تغییر و re render المنت ها می شه. بهتره از primary key عناصر موجود در لیست به عنوان key کامپوننت ها در نظر گرفته شود.
یک تکنیک برای استفاده از لیست های طولانی به صورتیکه همه لیست در ابتدا load نشه و بصورت دسته دسته و در صورت نیاز load شن میتونیم از react-window و یا react-virtualize استفاده کرد.
اگر در جایی و صفحه ای از کامپوننت های پیچیده و شلوغ استفاده میکنیم و یا حجم زیادی از کتابخانه ها استفاده میشه در load اولیه زمان بر خواهند بود برای جلوگیری از این اتفاق استفاده از خاصیت React lazy , suspense میتونه مفید باشه. ویژگی lazy به این معنی که هرجا نیاز به کامپوننت باشه load میشه و در همان اول همه کامپوننت ها load نمیشن که باعث کندی میشه. در زمانی که عملیات load شدن اتفاق می افته می تونیم با استفاده از suspense و ویژگی fallback بگیم در اون زمان چه کامپوننتی بطور موقت به نمایش دربیاد.
یکی دیگه از ابزارهای مفید در زمینه آنالیز کردن performance برنامه های react استفاده از webpack-bundle-analyzer عه.و برای مشاهده نتیجه باید در مود production اجرا شود. این ابزار تمام کتابخانه ها رو بررسی میکنه از لحاظ حجم و رابطه ها و تکراری بودن.
با توجه به موارد بالا متوجه میشیم که اگه نکات ریزی که تو محیط توسعه واسمون مشکل ساز نیست رو رعایت نکنیم در آینده و محیط production به دردسر خواهیم افتاد.