react در بیشتر مواقع عالی و سریع عمل میکند. اما گاهی اوقات به دلیل محاسبات سنگین سرعت آن کاهش مییابد، آن وقت است که برای جلوگیری از رندرهای بیهوده باید کامپوننتهای خود را بررسی و بهینه کنیم.
بهینه سازی هزینههایی به دنبال دارد. اگر به درستی انجام نشود، وضعیت ممکن است حتی بدتر هم شود. در این مقاله با روند رندرینگ آشنا میشویم و علت رندرهای بیهوده و راه حلها و چگونگی خراب شدن آن را میآموزیم.
فهرست مطالب
1. رندرینگ چیست؟ بررسی اجمالی فرآیند و مراحل آن
2. رفتار استاندارد رندر - علت هدر رفتن آنها در فاز رندر
3. بهبود عملکرد رندر - برخی از تکنیکها
4. چگونه ارجاعات جدید Propها بهینه سازی را از بین میبرند - جزئیات مشکل
5. بهینه سازی منابع propها: useMemo و useCallback
رندرینگ نوعی فرآیند در react است که از کامپوننتهای شما میخواهد آنچه که از ترکیب همزمان propها و state در رابط کاربری به وجود میآید را توصیف کنند.
بررسی اجمالی فرآیند
در طی این فرآیند، react از ریشه درخت کامپوننت شروع میشود و به سمت پایین حلقه میزند تا کامپوننتهایی را که به عنوان نیاز برای به روزرسانی نشانه گذاری میشوند، پیدا کند. برای هر یک از کامپوننتهای نشانه گذاری شده، ()render (برای کامپوننتهای کلاس) یا ()FunctionComponent (برای کامپوننتهای تابع) را فراخوانی کرده و خروجیهای رندر را ذخیره میکند.
خروجی رندر یک کامپوننت با JSX نوشته شده است. در صورت استفاده از render() یا ()FunctionComponent، در نهایت خروجی به عنصر react (ReactElement) تبدیل میشود که این عناصر در کنار هم برای تشکیل درخت مجازی استفاده میشوند.
پس از جمع آوری درخت جدید، react آن را متفاوت میکند. به این صورت که لیستهایی از همه تغییرات را که باید اعمال شود جمع میکند تا درخت واقعی مانند خروجی مورد نظر فعلی به نظر برسد. این روند Reconciliation نامیده میشود.
در بالا فرآیند بسیار اساسی برای ایجاد درخت میزبان (خروجی درخت) وجود دارد. درخت میزبان میتواند انواع مختلفی داشته باشد و بر پایه پلتفرمهای مختلف (وب، تلفن همراه و ...) باشد. دن آبراموف در اینجا توضیح بسیار خوبی برای آن شرح داده است.
تیم react فرایند بالا را به 2 مرحله تقسیم میکند:
توجه: اگر علاقهای ندارید میتوانید از این مرحله صرف نظر کرده و به خواندن ادامه دهید.
react native یک سلسله مراتب درختی را برای تعریف لایه اولیه انجام میدهد و یک تفاوت از آن درخت را در هر تغییر مانند طرح بالا ایجاد میکند. کامپوننت react native به روزرسانیهای رابط کاربری را از طریق چند لایه معماری مدیریت کرده و در پایان نحوه نمایش را نشان میدهد.
یوگا یک موتور لایه بندی کراس پلتفرم است که با زبان C نوشته شده و flexbox را از طریق اتصال به نماهای محلی (Java Android Views / Objective-C iOS UIKit) پیاده سازی میکند.
تمام محاسبات چیدمان نمایشها، متنها و تصاویر مختلف در react native از طریق یوگا انجام میشود. این اساسا آخرین مرحله قبل از نمایش بر روی صفحه است.
وقتی react native دستوراتی را برای رندر لایهها ارسال میكند، گروهی از گرههای سایه برای ساختن درخت سایه مونتاژ میشوند كه نمایانگر سمت بومی لایه است، سپس به نمایش واقعی روی صفحه (با استفاده از یوگا) ترجمه میشود.
ViewManger واسطی است که میداند چگونه انواع View ارسال شده از جاوااسکریپت را به کامپوننتهای رابط کاربری بومی آنها ترجمه کند. ViewManager همچنین میداند که چگونه یک گره سایه و یک گره view بومی ایجاد و آنها را به روز کند. در فریمورک react native، ViewManager زیادی وجود دارد که استفاده از کامپوننتهای بومی را امکان پذیر میکنند. اگر به عنوان مثال روزی خواستید یک نمای سفارشی جدید ایجاد کنید و آن را به react native اضافه کنید، این view باید رابط ViewManager را پیاده سازی کند.
UIManager آخرین قطعه از معما یا در واقع اولین مورد است. دستورات JavaScript JSX که به عنوان دستورات اجرایی به react native میگوید چگونه میتواند نمایشها را بصورت تکراری گام به گام تنظیم کند، به نیتیو ارسال میشود. بنابراین به عنوان اولین رندر، UIManager برای ایجاد ویوهای لازم دستور ارسال میکند و متناسب با تغییرات رابط کاربری برنامه که با گذشت زمان متفاوت است، به روزرسانی ارسال میکند.
بنابراین react native هنوز از توانایی react برای محاسبه تفاوت بین نمایش رندر قبلی و فعلی استفاده کرده و بر این اساس رویدادها را به UIManager ارسال میکند.
رفتار پیش فرض react این است که وقتی کامپوننت والد رندر میشود، react به صورت بازگشتی تمام کامپوننتهای فرزند را نیز درون آن رندر میکند.
به عنوان مثال ما یک درخت کامپوننت به صورت A> B> C داریم.
اکنون احتمالا اکثر کامپوننتها دقیقا مانند آخرین بار نتیجه رندر را برمیگردانند. بنابراین react نیازی به تغییر در درخت واقعی نخواهد داشت. با این حال react همچنان باید از کامپوننتهای سازنده بخواهد خودشان دوباره رندر شوند و خروجی را متفاوت کنند. هر دوی آنها زمان و تلاش زیادی میبرد، به ویژه هنگامی که کامپوننتها بزرگ هستند و محاسبات سنگینی دارند.
بنابراین فهمیدید که رندرهای بیهوده اینگونه اتفاق میافتد.
طبیعی است که انتظار میرود رندرها بخشی از react باشند. همچنین درست است که اگر خروجی رندر یک کامپوننت تغییر نکرده باشد و این قسمت از درخت نیازی به بروزرسانی نداشته باشد، گاهی اوقات تلاش هدر میرود.
رندر باید همیشه بر اساس Propها و state فعلی کامپوننت باشد. اگر زودتر بدانیم که Propها و State تغییر نمیکنند، خروجی رندر هم تغییر نخواهد کرد. بنابراین میتوانیم با خیال راحت از روند رندر آن کامپوننت صرف نظر کنیم.
وقتی نوبت بهینه سازی میرسد، میتوانید آن را سریعتر اجرا کنید یا حداقل کار کمتری انجام دهید. بیشترین بهینه سازی react مربوط به انجام کمتر کار است.
به یاد داشته باشید قبل از هرگونه بهینه سازی، اندازه گیری کنید تا مرتکب بهینه سازی زودرس نشوید.
react سه API اصلی برای رد شدن از رندر یک کامپوننت پیشنهاد میدهد.
همه این رویکردها از تکنیک مقایسهای به نام برابری کم عمق استفاده میکنند. این بدان معنی است که فیلد منفرد را در دو شی مختلف بررسی کرده و میبیند آیا محتوای شیها تفاوتی با هم دارد. این روش با === مقایسه را انجام میدهد و روشی ساده و سریع در موتور جاوااسکریپت محسوب میشود.
همانطور که در بالا با تکنیکهای برابری کم عمق آشنا شدیم، بدیهی است که عبور شیهای جدید در مقایسه با شکست مواجه میشود، زیرا "===" ارجاع را مقایسه میکند، حتی اگر محتوای آن تغییر نکرده باشد. این عمل بهینه سازیهای ما را میشکند، کامپوننت هم هنوز رندر میشود و تلاشهای بیشتری را هدر میدهد که از طریق مقایسه propها و درخت متفاوت است. پس مراقب باشید!
در این مثال، ما onClick و دادهها را به عنوان prop به MemoizedChildComponent منتقل میکنیم. اگرچه ChildComponent را بهینه سازی میکنیم، اما این برنامه همچنان هر به روزرسانی ParentComponent را دوباره رندر میکند. زیرا propهای MemoizedChildComponent هر بار شیهای جدیدی به دست میآورند.
انتظار میرود که MemoizedChildComponent رندر را رد کند، زیرا محتوای propهایش یکسان است. بیایید ادامه دهیم و بفهمیم که چگونه میتوانیم این مشکل را برطرف کنیم.
کامپوننتهای کلاس نگران ایجاد تصادفی ارجاعات شی بازگشتی جدید نیستند، زیرا آنها میتوانند متدهای نمونهای داشته باشند که همیشه همان ارجاع باقی بمانند. با این حال ممکن است لازم باشد برای موارد جداگانه لیست فرزند، پاسخهای منحصر به فردی ایجاد کنند یا مقداری را در یک تابع ناشناس ضبط کرده و آن را به فرزند منتقل کنند که منجر به ایجاد شیهای جدید میشود. react برای بهینه سازی موارد مذکور با هیچگونه ابزار داخلی همراه نیست.
کامپوننت تابع، react دو قلاب useCallback (برای فراخوانی تابع) و useMemo (برای هر نوع دادهای مانند ایجاد اشیا یا محاسبات پیچیده) را پیشنهاد میدهد. در این مقاله میتوانید تفاوت بین این دو هوک را همراه با جزییات مطالعه کنید.
هدف این مقاله این است که مشکل را برطرف کنید، نه آموزش hook. به اعتقاد من منابع زیادی وجود دارد که قلاب را به خوبی توضیح دادهاند. بنابراین در اینجا به جزئیات نمیپردازیم. اما میتوانید مقدمهای سریع بر hook را در اینجا بخوانید.
هر بهینه سازی با هزینه خاص خود همراه است. بهینه سازی با بی دقتی منجر به بدتر شدن عملکرد میشود. همیشه ابتدا با React devtool یا هر ابزار دلخواه خود اندازه گیری کنید، گلوگاه را پیدا کنید و سپس بهینه سازی را انجام دهید.
این کار همیشه سودمند نیست، اگر بود react آن را به عنوان پیش فرض اجرا میکرد، درست است؟
چرا react به طور پیش فرض memo() را در مورد هر کامپوننت قرار نمیدهد؟ این کار سریعتر نیست؟ آیا باید معیاری برای بررسی ایجاد کنیم؟ از خود بپرسید که چرا ()Lodash memoize را برای هر تابع قرار نمیدهید؟ آیا این باعث سریعتر شدن همه توابع نمیشود؟ آیا برای این کار به معیاری نیاز داریم؟ چرا که نه؟
جمع بندی
به طور خلاصه فرآیند رندرینگ در react به دلیل به روزرسانی کامپوننتهای والد، کامپوننت سازنده فرزندان را نیز رندر میکند، این بد نیست زیرا react از این طریق تغییرات را میداند و گاهی اوقات تلاش برای رندر هدر میرود.
صرف نظر کردن از رندر یک روش معمول برای بهینه سازی این موضوع است و این کار به ارجاعات propها مربوط میشود. پس با دقت بهینه سازی کنید و بهینه سازی زودرس انجام ندهید.
ارجاع propها بیشتر از اینها دارای مشکل است. میتوانید این مقاله را در مورد چگونگی تأثیر آن بر وابستگی در استفاده از قلاب مطالعه کنید.
امیدوارم این مقاله برایتان مفید واقع شود. در صورت داشتن هرگونه سوال آن را در بخش زیر حتما با ما در میان بگذارید.
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
بخشی برای حل مشکلات برنامهنویسی و مباحث پیرامون آن وارد شو