اشتباه رایج استفاده از OnPush در انگولار

فهرست مطالبمقدمهنگاهی به Change Detectionمرور تفاوت فراخوانی متغیرها با reference و valueبررسی عملکرد OnPushاشتباهات رایج در OnPushنتیجهمقدمهیکی از عوامل تاثیرگذار در کارایی یا performance اپلیکیشن های انگولار معماری Change Detection هست. مهمه که با این مفهوم و استراتژی های موجود آشنا بشیم. استفاده از استراتژی OnPush میتونه تاثیر بسزایی در بهبود عملکرد اپلیکیشن ها داشته باشه. اما اگر درست متوجه عملکردش نشیم ممکنه به مشکلاتی بربخوریم و بیخیال استفاده ازش بشیم. در این مقاله میخوایم بیخیالش نشیم و ازش درست استفاده کنیم.نگاهی به Change Detectionدر بخش اول میخوایم ساختار Change Detection در انگولار رو بررسی کنیم. اول ببینیم وظیفه change detection چیه. بعد ببینیم ساختارش چطوریه.وظیفه change detection در اپلیکیشن انگولار چیه؟وظیفه change detection اینه که وقتی دیتایی تغییر کرد، متوجه بشه و نمایش کامپوننت ها در صورت لزوم و بر اساس دیتای جدید آپدیت بشه.ساختار change detection چطوریه؟ اپلیکیشن های انگولار ساختار درختی و سلسله مراتبی از کامپوننت ها دارند.در زمان runtime انگولار برای هر کامپوننت یک کلاس change detector میسازه. در نتیجه کلاس های change detector ساختاری درختی و سلسله مراتبی شبیه به کامپوننت ها پیدا میکنند. در هر سیکل، change detection شروع به چک کردن همه change detector ها میکنه و می بینه که آیا تغییری اتفاق افتاده یا نه.به ازای هر تغییری که متوجه بشه، وضعیت قبلی و فعلی همه کامپوننت ها رو چک میکنه و چنانچه تغییری ببینه، اون کامپوننت خودش رو re-render میکنه.استراتژی های مختلف هر کامپوننت برای صدا کردن change detection ، در واقع صرفه جویی در سرعت هر سیکل change detection هست. قبل از اینکه به OnPush برسیم بریم سراغ مرور تفاوت فراخوانی متغیرها با Reference و Valueدر بخش دوم میخوایم به صورت اجمالی مرور کنیم ببینیم فراخوانی متغیرها با reference و با value چه تفاوتی داره. دلیلش اینه که با مرور این مطلب، عملکرد ChangeDetectionStrategy.OnPush رو بهتر متوجه میشیم. اگر روی این بخش مسلط هستی میتونی بری سراغ بخش بعدی...برای شروع، یک مروری به تایپ های متغیر و دسته بندی هاشون داشته باشیم.انواع value types عبارتند از:BooleanNullUndefinedNumberStringمقدار این متغیر ها در حافظه stack ذخیره میشه و ما با داشتن آدرس حافظه به راحتی میتونیم به مقدارشون دسترسی داشته باشیم.انوع reference types عبارتند از:ArraysObjectsFunctionsنگهداری این تایپ ها در حافظه یه مقدار پیچیده تر از نگهداری value type هاست. این متغیرها مقدار خود را در heap memory نگه میدارند و رفرنس به حافظه heap را در حافظه stack نگهداری میکنند. بنابر این برای خواندن مقدار آنها باید ابتدا در حافظه stack به دنبال مقدار رفرنس بگردیم و بعد به کمک رفرنس مقدار را از حافظه heap بخوانیم.بنابر این در متغیرهایی از نوع value type، ما با خواندن stack memory متوجه میشیم که مقدارش تغییر کرده یا نه. ولی در متغیرهای نوع reference type با خواندن stack memory فقط متوجه تغییر آدرس رفرنس میشیم نه مقدار متغیر. در واقع ممکنه مقدار متغیر در heap memory تغییر کنه ولی مقدار رفرنس متغیر در stack memory دست نخورده باشه.بررسی عملکرد ChangeDetectionStrategy.OnPushبا مرور بخش قبل راحت تر میتونیم عملکرد OnPush رو درک کنیم. در استراتژی پیش فرض انگولار، زمانی که change detection میخواد تغییرات رو چک کنه، نگاه میکنه ببینه مقدار value type ها در stack memory و مقدار reference type ها در heap memory تغییر کرده یا نه.اما در استراتژی OnPush و برای ایجاد سرعت بیشتر در change detection فقط تغییر مقادیر stack memory رو بررسی میکنه. بنابر این در متغیرهای reference type تنها تغییر رفرنس شون بررسی میشه و نه مقدارشون.بریم با یک مثال ببینیم که در آپدیت یک آبجکت چه تفاوتی بین تغییر مقدار و تغییر رفرنس هست:نمونه تغییر مقدار و رفرنس آبجکت در مثال بالا کل آبجکت مقدار جدیدی میگیره. در این حالت هم مقدار رفرنس در stack memory تغییر میکنه و هم مقدار متغیر در heap memory.اما در مثال زیر:نمونه تغییر مقدار آبجکت بدون تغییر مقدار رفرنس تنها یک پارامتر متغیر ( بنام foo ) عوض شده و این تغییر در heap memory اتفاق افتاده درحالیکه در stack memory مقدار رفرنس متغیر هیچ تغییری نکرده. بنابر این متغیرهای before و current حاوی رفرنس های یکسانی هستند و به یک آبجکت در heap memory اشاره میکنند و برابرند.اشتباه رایج در OnPush اشتباه رایج در OnPush زمانی رخ میده که ما فقط مقدار متغیر رو عوض میکنیم و رفرنس متغیر عوض نمیشه. در این شرایط استراتژی OnPush متوجه تغییرات نمیشه و تغییری رو اعلام نمیکنه. مثال زیر این موضوع رو واضح تر میکنه.فرض کنید یک کامپوننت داریم به اسم app-newsletter که نام کاربر رو به عنوان ورودی از کامپوننت بالادستیش میگیره و وقتی دکمه subscribe زده میشه، ایمیل کاربر رو برای کامپوننت بالادستیش میفرسته. کامپوننت بالادستیش هم مثلا کامپوننت پروفایل هست که اطلاعات کاربر ( آبجکت کاربر ) رو برای newsletter میفرسته و ایمیل رو ازش دریافت میکنه و ایمیل کاربر به سرویس newsletter پاس میده تا کاربر subscribe بشه.دقت کنید که بار اول، در کامپوننت newsletter از استراتژی پیش فرض انگولار استفاده میکنیم.اول یه نگاهی کنیم به کامپوننت newsletter:و یک نگاهی کنیم به کامپوننت profile:وقتی برنامه رو اجرا میکنیم و روی دکمه change user name کلیک میکنیم، میبینیم که نام کاربر در کامپوننت newsletter تغییر میکنه و همه چیز درست کار میکنه.شروع مشکلوقتی استراتژی کامپوننت newsletter رو به OnPush تغییر میدیم ، با کلیک کردن روی دکمه change user name دیگه نام کاربر در کامپوننت newsletter عوض نمیشه و از کار می افته.مشکل کجاست؟مشکل در تابع changeUserName در ProfileComponent هست، وقتی که فقط فیلد آبجکت user رو عوض میکنیم و رفرنس آبجکت تغییر نمیکنه، در حالیکه با تغییر استراتژی change detection در کامپوننت newsletter، اون رو به تغییر رفرنس حساس کردیم و به مقدار value آبجکت نگاه نمیکنه.مشکل رو چطوری حل کنیم؟ با تغییر تابع changeUserName به شکل زیر:در واقع ما هم مقدار و هم رفرنس آبجکت user رو تغییر میدیم تا کامپوننت newsletter متوجه تغییرات آبجکت کاربر بشه.نتیجهدر این مقاله ساختار Change Detection رو توضیح دادیم. تفاوت فراخوانی متغیرها با Reference و با Value رو مرور کردیم. عملکرد OnPush رو بررسی کردیم و اشتباه رایج در استفاده از OnPush رو اصلاح کردیم.موفق باشید :)