با استفاده از Profiler، ری‌اکت را بهینه تر بنویسید!

مقدمه

با React ساختن وب اپلیکیشن ها از همیشه بهتر و بهینه تر شد. یکی از علت های بی‌شمار اون هم استفاده React از مفهومی به نام Virtual DOM یا دام مجازی هست که باعث میشه اون قسمت هایی از نود های DOM که توسط یک سری فعالیت های کاربر (user-event) تغییر کرده سریع تر شناسایی بشه !

حالا اگر برنامه ای که شما با ری اکت نوشتید نتونه ۶۰ فریم در ثانیه رو پوشش بده باید نسبت به استانداردهایی که در برنامه نویسی این وب اپ رعایت کردید مشکوک بشید :)

خیلی بدیهیه که اگر وب اپ شما کند بشه برای انجام دستور العمل های کاربر زمان بیشتری طول خواهد کشید و همین امر منجر به یک تجربه ی کاربری ضعیف میشه که میتونه روی سئو شما تاثیر منفی به سزایی بزاره.

و اما ، React Profiler چیست؟

و اما مشکلاتی که در بالا گفتیم می تونه به علت های مختلفی پیش بیاد مثلا رندر شدن بیش از اندازه و غیر نیاز بعضی کامپوننت ها و یا مدیریت نشدن درست برخی state ها و ... که ما تصمیم داریم با Profiler اونا رو ردیابی کنیم و با شناختشون بتونیم وب اپ بهینه تری داشته باشیم.

The Profiler measures how often a React application renders and what the “cost” of rendering is. Its purpose is to help identify parts of an application that are slow and may benefit from optimizations such as memoization

طبق تعریف داکیومنت ری اکت، پروفایلر میخواد به ما بگه که اپلیکیشن ری اکتی ما چطور کار می کنه و هزینه ی هر رندرش برای ما چقدر هست و هدفش اینه که به ما کمک کنه که قسمت هایی از اپلیکیشنمون که کند هست و یا احتیاج به بهینه کردن داره رو بشناسیم و بتونیم با انتخاب روش هایی اون ها رو بهینه تر کنیم ( مثل استفاده از useMemo برای هوک ها، memo برای function component ها، و یا استفاده از pure component یا lifecylce ای مانند shouldComponentUpdate برای کلاس کامپوننت ها.)

پ.ن : برای آشنایی بهتر با اصطلاحات بالا میتونید این مقاله عامر رو بخونید.

نحوه استفاده Profiler در یک برنامه ری اکتی

این Api در نسخه ی 16.5 قرار گرفت و تا نسخه ی 16.9 یک experimental API بود ( یک API تجربی که برای ایمپورت کردن باید ابتدا unstable رو می نوشتید )

خوشبختانه در زمان نگارش این متن که نسخه ی 16.9 ری اکت هستیم این API به صورت کاملا ثابت منتشر شده و کافیه برای ایمپورت از دستور زیر استفاده کنید.

import {Profiler} from 'react'

البته که می تونید از React.Profiler هم استفاده کنید و خط بالا رو ننویسید :)

این کامپوننت می تونه هرجای برنامه ری اکتی شما بشینه و کامپوننت های شما رو wrap کنه.

به عنوان مثال :

در حال پروفایل کردن کامپوننت Navigation
در حال پروفایل کردن کامپوننت Navigation

در مثال بالا Profiler در حال wrap کردن کامپوننت Navigation هست.

اما حالت های دیگه ای هم وجود داره مثل استفاده از Profiler های چندگانه و یا Profiler های تو در تو که در زیر می بینید :

پروفایلر های چندگانه
پروفایلر های چندگانه
پروفایلرهای تو در تو برای اندازه گیری هزینه قسمت های مختلف اپلیکیشن
پروفایلرهای تو در تو برای اندازه گیری هزینه قسمت های مختلف اپلیکیشن
اگرچه پروفایلر یک کامپوننت اصطلاحا سبک وزن به حساب میاد اما در استفاده از اون دقت کنید! چون مقداری CPU و Memory سربار به اپلیکیشن شما اضافه می کند.

بررسی Profiler Props

همونطور که در تمامی مثال های بالا دیدید این کامپوننت ۲ تا Prop داره که id و onRender هستند.

خب id مشخص کننده ی این هست که این نتیجه ای که بعدا میبینیم چیه مربوط به کدوم پروفایلر هست.

و اما onRender : یک callback function هست که ری اکت هر بار کامپوننتی که توسط پروفایلر ، wrap شده ، commit update میشه صداش می زنه تا اطلاعاتی در مورد چگونگی رندر و زمانی که طول کشیده به ما بده.

ممکنه این توضیح یکم گیجتون کرده باشه پس سعی میکنم کامل تر و جزئی تر توضیح بدم. به مثال اولیمون برگردین. کامپوننتی که پروفایلر اون رو wrap کرده Navigation هست. هر زمان که این کامپوننت آپدیت بشه ( یا حتی mount بشه ) ، ری اکت میاد و onRender رو صدا می زنه.

پارامترهای onRender :

حال به بررسی تک تک پارامتر های آن می پردازیم :

  • پارامتر id = مشخص می کنه دقیقا اطلاعاتی که لاگ میندازید مربوط به کدوم پروفایلر هست ( اشاره به پروفایلرهای چندگانه و تو در تو )
  • پارامتر phase = دارای دو مقدار mount و update هست. کلا یک کامپوننت در ری اکت در یکی از این ۲ فاز قرار داره که یا برای اولین بار mount شده و یا بر اثر تغییر props هاش و یا state ها update شده. پس همیشه هنگام لاگ انداختن این پارامتر انتظار یکی از این مقادیر رو داشته باشید.
  • پارامتر actualDuration = پروفایلر و تمام فرزندانش رو درنظر بگیرید. زمان سپری شده برای آپدیت فعلی رو با این پارامتر می تونید بدست بیارید. نکته ای که حائز اهمیت هست اینه که در حالت ایده آل انتظار میره که actualDuration در فاز mount بیشترین مقدار رو داشته باشه و در باقی حالات مقدارش کمتر باشه ( به علت اینکه در آپدیت های بعدی ممکنه بعضی از فرزندان نیاز به رندر شدن نداشته باشند ) همچنین اینجا جاییه که می تونید به استفاده کردن از اون حالت های بهینه ی ابتدای مقاله فکر کنید.
  • پارامتر baseDuration = پروفایلر و تمام فرزندانش رو در نظر بگیرید. زمان سپری شده برای هر کامپوننت در آخرین رندر رو به ما میده. نکته هایی که وجود داره اینه که اگر یک کامپوننت ری-رندر نشه از ارائه ی زمان برای این کامپوننت خودداری میکنه. و اگه زمان ارائه شده در رندرهای بعدی کمتر از زمان ارائه شده در یک commit update خاص باشه از نمایش اون ها هم صرف نظر می کنه به همین دلیل هست که Brain Vaughn اعتقاد داره که این مقدار در واقع بدترین زمان برای رندر رو برآورد می کنه.
  • پارامتر startTime = زمانی که ری اکت شروع به رندر میکنه رو به صورت TimeStamp در اختیار ما قرار میده.
  • پارامتر commitTime = زمانی که آپدیت کاملا انجام شده رو به صورت TimeStamp در اختیار ما قرار میده و اگر چندین پروفایلر رندر بشن این زمان بین همشون مشترک خواهد بود و در صورت تمایل میتونید اون ها رو گروه بندی کنید.
  • پارامتر interaction = زمانی که متد render و یا setState در کلاس ( و معادل هاشون در Hook ) صدا زده میشه این پارامتر میاد و علت به روزرسانی رو مشخص میکنه. که هنوز به صورت experimental API هست.


اضافه شدن Profiler به React Dev Tools

علاوه بر مشاهده ی گزارشات توسط لاگ انداختن در متد onRender ، میتونیم از افزونه ی محبوب React Dev Tools برای برنامه های ری اکتیمون استفاده کنیم و خیلی چیز ها رو به صورت ویژوال ببینیم.

پس اول این افزونه رو بسته به مرورگرتون دانلود و نصب کنید.

برای کروم

برای فایرفاکس ♥️

پس از نصب و استفاده از پروفایلر پروژتون رو ران کنید و با inspect گرفتن دو تا تب می بینید که مربوط به این افزونه هست : یکی component و دیگری profiler که خب ما در این جا با دومی کار داریم :)

برای پروفایل کردن کافیه که روی دایره آبی کلیک کنید تا رکورد شروع بشه و بعد از شروع شدن رکورد کاملا عادی و بدون استرس با اپلیکیشنتون کار کنید :دی

زمانی که کارتون تموم شد و یا اون بخش های مدنظر اپتون رو تست کردید ریکورد رو استاپ کنید و به اینجا برگردین تا باهم بتونیم نتایجی که میده رو درست بخونیم و تحلیل کنیم :)

نتایجی که به من در هنگام استفاده از یک Counter ساده داد اینه :

حالا بریم این اطلاعات رو تقسیر کنیم.

پیمایش بین آپدیت ها یا همان Browsing Commits

در این قسمت هر کدام از این مستطیل ها یک single commit هستند و می تونید با کلیک بر روی هر کدوم اطلاعات مربوط به هر commit رو جداگانه ببینید.

حجم و رنگ هر کدوم از این مستطیل ها نشان دهنده ی هزینه ی رندر و زمان سپری شده است.

اصولا رنگ زرد بیشترین هزینه و رنگ آبی کمترین هزینه رو داره :)

فیلتر کردن Commit ها

با کلیک روی چرخ دنده و بعد انتخاب تب Profiler به تنظیمات جالبی می رسید.

- گزینه ی اول رو در صورتی که تیک بزنید باعث میشه که علت هر رندر رو هنگام پروفایل کردن ریکورد کنه.

- گزینه ی دوم و کاربردی تر اینه که شما فرضا می خواید بدونید کامپوننت هایی که از ۱۶ میلی ثانیه بیشتر طول می کشند و باعث مموری لیک می شوند رو شناسایی کنید پس این مقدار رو بر روی ۱۵ قرار میدید و یه فیلترینگ خوب خواهید داشت.

با نقل قول از عامر عزیز باید بگم تا قبل از وجود این افزونه برای بحث بهینه بودن باید تب Performance رو چک می کردیم که در اون تب هم تغییرات کلی DOM داده می شد و نه کامپوننت یا V-DOM

Graph
Graph

بررسی Flamegraph

در این تب از زیر مجموعه ی Profiler در افزونه ی React Dev Tools مستطیل هایی رو می بینید که هرکدوم نمایانگر یک کامپوننت هستند. که همانطور که پیشتر عرض کردم رنگ و سایز هر کدوم از این مستطیل ها بیانگر مدت زمانی است که آخرین رندر این کامپوننت و فرزندان آن به طول انجامیده است. که با کلیک بر روی هر کدوم از کامپوننت ها در سمت راست یک تب شامل اطلاعات Commit Information می بینید.

توجه داشته باشید که طول هر مستطیل بیانگر زمان آخرین رندر کامپوننت و فرزندانش هست پس اگر یک کامپوننت در Commit های بعدی مجددا رندر نشه باز هم عدد مربوط به آخرین رندر اون کامپوننت و فرزندانش نمایش داده میشه.
نکته ی دیگه هم اینه که رنگ های خاکستری ای که ملاحظه می کنید بیانگر این است در این Commit رندر نشده اند.

بررسی Ranked Chart

در این چارت هر کدام از مستطیل ها نمایانگر یک کامپوننت هستند که به ترتیب زمان سپری شده ( نزولی ) مرتب شدند.

یک نکته قابل ذکره، مدت زمانی که با استفاده از Profiler نمایش داده میشه ، در اصل مدت زمانی هست که کامپوننت و فرزندانش رندر شدند پس طبیعیه که هرچه کامپوننتی مدت زمان بیشتری طول بکشه فرزندان بیشتری داره یا به عبارتی دیگه به شاخه ی درخت Profiler نزدیک تره.

بررسی Interaction

به سراغ تب سوم میریم :)

همونطور که بالاتر هم گفتیم ری اکت اخیرا یک experimental API برای بررسی علت آپدیت ها اضافه کرده که همونطور که در تصویر پایین مشخص هست علت هر آپدیت رو مشخص میکنه.

برای توضیح بیشتر باید عرض کنم که یک تابعی به نام trace که دارای ۳ پارامتر هست میتونه به دور render شما بشینه و یا هنگام به روزرسانی state ها استفاده بشه و یا حتی میتونه به صورت async استفاده بشه.
این سه پارامتر هم به ترتیب یک string برای علت آپدیت شدن هست که بعدا قراره به ما در Profiler نشون داده بشه. پارامتر دوم تابع performance.now هست که زمانی که یک آپدیت commit میشه یا ثبت میشه این تابع صدا زده میشه ( بعدا ازش چیزهای بیشتری خواهم نوشت ) و یک callback function هست.

هر کدام از این نقطه ها نمایانگر Commit ای هستند که مربوط به Interaction مربوطه بوده.

بررسی React Profiler در پروداکشن

همونطور که در بالاتر هم گفتیم پروفایلر به علت اضافه کردن مقداری اورهد به برنامه ی شما به صورت پیش فرض در نسخه ی پروداکشن غیرفعال شده.

اما اگه همچنان اصرار دارید که در نسخه پروداکشن از پروفایلر استفاده کنید ری اکت یک کانفیگ ویژه برای شما در نظر گرفته :)

در صورتی که پروژه ی شما با CRA بالا اومده تنها راهی به صورت همیشگی پروفایلر در پروداکشن شما فعال بشه ایجکت کردن هست و سپس وب پک رو به در مسیر زیر تغییر بدید :

config/webpack.config.prod.js

حالا تغییرات زیر رو در این فایل انجام بدید :

module.exports = {
  //...
  resolve: {
    alias: {
      'react-dom$': 'react-dom/profiling',
      'scheduler/tracing': 'scheduler/tracing-profiling',
    }
  }
};
نکته ی اول اینکه حتما ورژن react-dom شما باید حداقل 16.6.0 باشه.
نکته ی دوم اینکه حتما ورژن scheduler شما باید حداقل 0.10.0 باشه


بررسی React Profiler در React-Native

تا به این لحظه ( زمان نگارش مقاله ) امکان استفاده از این api در ری اکت نیتیو وجود ندارد و به محض اینکه اطلاعات بیشتری در این مورد بدست بیاورم در این پست برای RN ای ها خواهم نوشت :)


امیدوارم که خستتون نکرده باشم و این مطلب به دردتون بخوره.

هر سوالی داشتید من در خدمتتون هستم ♥️


Linked-in : mohammad-vahedi

Twitter : @mvahediiii

mail : vahedi.ce@gmail.com