آموزشگاه برنامه نویسی تحلیل داده
آموزشگاه برنامه نویسی تحلیل داده

آموزش عملکرد (Performance) در React Native Apps

دوره های مرتبط با این مقاله

عملکرد (Performance) در React Native Apps

یک دلیل مهم برای استفاده از React Native به جای ابزارهای مبتنی بر WebView ، رسیدن به 60 frame در ثانیه است درحالی که application حس و ظاهر یک application native را داشته باشد . تا حد ممکن، هدف ما این است که با React Native، شما روی application تان تمرکز کنید نه بهبود عملکرد آن، اما مواردی وجود دارد که باید آن را دستی مدیریت کنید. ما به بهترین وجه تلاش می کنیم تا به طور پیش فرض عملکرد روانی برای UI ارائه دهیم، اما گاهی اوقات این امکان وجود ندارد.

هدف این بخش آموزش اصولی است که که از طریق آن مشکلات عملکرد application خود را رفع کنید، دلایل بروز آن ها و راه حل های پیشنهادی را بدانید.

آنچه در مورد frame ها باید بدانید

نسل قدیم به دلیلی موجه فیلم ها را "تصاویر متحرکت" نامیده است: حرکت به ظاهر واقعی در video، ناشی از تغییر سریع تصاویر ثابت با سرعتی ثابت است. هر یک از این تصاویر را frame می نامیم. تعداد frame هایی که هر ثانیه نمایش داده می شود تاثیر مستقیمی بر روان بودن video (یا رابط کاربری) و حقیقی بودنش دارد. دستگاه های iOS ، frame 60 در ثانیه نمایش می دهند، که به شما و سیستم UI حدود 16.67 میلی ثانیه زمان می دهد تا تمام کارهایی که برای تولید یک frame لازم است، انجام دهید.

اگر قادر به انجام کار لازم برای تولید آن frame در 16.67 میلی ثانیه زمان اختصاص داده شده به آن نباشید، frame اصطلاحا drop می شود و UI اصطلاحا responsive نیست.

برای پیچیده کردن موضوع، درون application تان منوی developer را باز کنید و Show Perf Monitor را بزنید. می بینید که دو rate frame متفاوت وجود دارد.


Show Perf Monitor

rate frame در جاوااسکریپت (JavaScript thread)

برای اکثر برنامه های React Native، منطق کسب و کار روی JavaScript thread اجرا می شود. این جایی است که فراخوانی API ها انجام می شود، touch event ها پردازش می شوند، و غیره.

بروزرسانی view های native در انتهای یک event loop، باهم به سمت کد native فرستاده می شود (اگر همه چیز خوب پیش رود). اگر JavaScript thread برای یک frame، responsive نباشد، آن frame اصطلاحا drop شده است. برای مثال، اگر this.setState را در یک root component یک application سنگین فراخوانی کنید و این کار موجب render دوباره ی تمام زیرمجموعه های component شود، ممکن است 200ms طول بکشد و در نتیجه frame 12 از دست برود. هر انیمیشن که توسط جاوا اسکریپت کنترل می شود، در این بازه متوقف می شود. هر وقفه ای بیش از 100 میلی ثانیه را کاربر متوجه می شود.

این اغلب در طول حرکات Navigator اتفاق می افتد: هنگامی که یک route جدید (route یک شی Javascript است که از یک صفحه به صفحه دیگر منتقل می شود) را push می کنید، JavaScript thread باید تمام component های مورد نیاز را render کند تا دستور مناسب را برای ساخت viewها به سمت پلتفرم native بفرستد. اغلب انجام این کار، چند frame زمان می برد و موجب وقفه شود چرا که حرکت توسط JavaScript thread کنترل می شود. گاهی اوقات component ها کار اضافی را در ComponentDidMount انجام می دهند، که ممکن است موجب وقفه ی دوم در حرکت شود.

مثال دیگر واکنش به touch است: اگر در حال انجام کار در frame های متعدد روی JavaScript thread هستید، ممکن است تاخیری در پاسخ به TouchableOpacity احساس کنید. این به این دلیل است که JavaScript thread مشغول است و نمی تواند touch event پردازش نشده که از thread اصلی می گیرد را پردازش کند. در نتیجه، TouchableOpacity نمی تواند به touch event واکنش نشان دهد و view native را برای تغییر opacity تنظیم کند.

UI frame rate (main Thread)

بسیاری از افراد متوجه شده اند که عملکرد NavigatorIOS بهتر ازNavigator است. دلیل این امر این است که انیمیشن ها برای transition ها کامل روی thread اصلی انجام می شود. از دست رفتن frame ها روی JavaScript thread موجب وقفه آن ها نمی شود.

به طور مشابه، هنگامی که JavaScript thread قفل می شود همچنان می توانید بدون هیچ مشکلی با ScrollView به بالا و پایین حرکت کنید، زیرا event های ScrollView روی thread اصلی اجرا می شوند. Scroll event ها به JavaScript thread ارسال می شوند، اما دریافت آن ها لازمه انجام حرکت نیست.

دلایل رایج مشکلات performance

اجرا در حالت توسعه (dev=true)

عملکرد JavaScript thread زمانی که در حالت dev اجرا می شود بشدت ضعیف است. این امر اجتناب ناپذیر است: در زمان اجرا کارهای زیادی انجام می شود، مثل هشدارهای مناسب و پیام های خطا، مانند validation propTypes ودیگر ارزیابی ها. همیشه عملکرد application را در release build ها تست کنید.

استفاده از console.log

هنگام اجرای یک برنامه ، این قبیل دستورات می تواند JavaScript thread را block کند. این مشکل شامل فراخوانی ها به library های ویژه debug مثل نیز می شود. قبل از publish کردن application، آن ها را حذف کنید.

همچنین می توانید از این babel plugin برای حذف همه دستورات console.* استفاده کنید. ابتدا باید آن را با دستور npm i babel-plugin-transform-remove-console --save-dev نصب کنید و سپس فایل .babelrc در پوشه پروژه خود را به صورت زیر ویرایش کنید:


{
  "env": {
    "production": {
      "plugins": [ "transform-remove-console" ]
    }
  }
}

در نسخه release پروژه، تمام console.* ها به طور خودکار حذف می شوند.

render اولیه ListView بسیار کند است و scroll برای لیست های بزرگ عملکرد خوبی ندارد

از component های جدید FlatList یا SectionList استفاده کنید. علاوه بر ساده سازی API، عملکرد List component های جدید نیزبهبود قابل توجهی داشته است، اصلی ترین آنها استفاده از حافظه تقریبا ثابت برای هر تعداد row است.

اگر FlatList به کندی render می شود، حتما برای بهینه سازی سرعت render با نادیده گرفتن اندازه ی آیتم های render شده، getItemLayout را پیاده سازی کنید.

هنگام render دوباره ی یک view که چندان تغییری نکرده، JS FPS بشدت افت می کند

اگر از ListView استفاده می کنید، باید یک تابع rowHasChanged داشته باشید که با تعیین سریع اینکه یک row باید دوباره render شود یا نه، عملیات ها را کم می کند. اگر از data structure های immutable استفاده می کنید، اینکار به سادگی مقایسه ی تساوی دو reference است.

به طور مشابه، می توانید ComposeUpdate را پیاده سازی کنید و شرایط دقیق دوباره render شدن component را تعیین کنید. اگر component هایی بنویسید که در آن مقدار بازگشتی تابع render کاملا وابسته به props و state است، می توانید از PureComponent استفاده کنید. در این صورت هم، data structure های immutable روی سرعت اثر می گذارد- اگر می خواهید روی لیستی بزرگ از اشیا deep comparison انجام دهید، render دوباره ی کل component سریعتر است و کد کمتری لازم خواهد داشت.

افت JS thread FPS به دلیل کار زیاد روی JavaScript thread

"Slow Navigator transitions" شایع ترین علامت این مشکل است، اما زمان های دیگری نیز می تواند رخ دهد. استفاده از InteractionManager می تواند رویکرد مناسبی باشد، اما اگر UX مهم تر است و نمی خواهید حین اجرای یک انیمیشن کاری به تعویق بیوفتد، بهتر است از LayoutAnimation استفاده کنید.

Animated API در حال حاضر هر keyframe را در لحظه روی JavaScript thread محاسبه می کند، مگر اینکه useNativeDriver: true را مقداردهی کنید. LayoutAnimation از Core Animation استفاده می کند و از دست رفتن frame ها روی آن بی تاثیر است.

یکی از موارد استفاده از LayoutAnimation، متحرک سازی یک modal و احتمالا دریافت پاسخ request ها، render کردن محتوای modal ، و بروزرسانی view که modal از آن جا باز شده، است. برای اطلاعات بیشتر در مورد چگونگی استفاده از LayoutAnimation، بخش کار با انیمیشن ها در React Native را ببینید.

هشدارها:

LayoutAnimation فقط برای انیمیشن static کار می کند - اگر باید انیمیشن باید قابل pause کردن باشد، باید از Animated استفاده کنید.

حرکت یک view روی صفحه (scroll، تغییر translate ، چرخش rotate ) باعث افت UI thread FPS می شود

این به خصوص هنگامی صحت دارد که متنی با یک background transparent روی یک تصویر داریم ، یا هر وضعیت دیگر که در آن alpha compositing برای مجدد draw کردن view روی هر frame، لازم است. فعال کردن shouldRasterizeIOS یا renderToHardwareTextureAndroid می تواند کمک خوبی برای این مشکل باشد.

اما استفاده زیاد از آن موجب افزایش مصرف memory میشود. performance و میزان مصرف memory هنگام استفاده از این prop ها را بررسی کنید. اگر قصد حرکت دادن view را ندارید، کلا آن را غیرفعال کنید.

متحرک کردن اندازه ی یک تصویر باعث افت FPS UI می شود

در iOS، هر بار که width یا height یک component image را تنظیم می کنید، نسبت به تصویر اصلی دوباره scale یا crop می شود. این کار مخصوصا برای تصاویر بزرگ سنگین است. به جای آن از transform: [ {scale} ] استفاده کنید. یک مورد استفاده وقتی است که بخواهید با دوبار Tap روی عکس آن را zoom کنید.

Touchable view واکنش نمی دهد

گاهی اوقات، اگر کاری را در همان frame انجام دهیم که داریم opacity یا highlight یک component که درحال پاسخ دادن به حرکت کاربر است، را تغییر می دهیم، تغییر را تا زمان اتمام تابع onPress نخواهیم دید. اگر onPress یک setState انجام دهد، که باعث انجام چند کار و drop شدن چند frame شود، این مشکل پیش می آید. یک راه حل، قرار دادن هر تابعِ درون onPress handler، درون requestAnimationFrame است:


handleOnPress() {
  requestAnimationFrame( () = > {
    this.doExpensiveAction();
  });
}

حرکت کند navigator

همانطور که در بالا ذکر شد حرکات Navigator توسط JavaScript thread کنترل می شود. scene حرکت از راست را تصور کنید: درهر frame، scene جدید از سمت راست به چپ حرکت می کند و از خارج صفحه شروع می کند (مثلا در x-offset = 320).

برای هر frame در هنگام این حرکت، JavaScript thread باید x-offset جدید را به thread اصلی ارسال کند. اگر JavaScript thread مشغول باشد، update روی آن frame اتفاق نمی افتد و حرکت دچار وقفه می شود.

یک راه حل این است که انیمیشن های مبتنی بر جاوا اسکریپت را به thread اصلی بسپاریم. اگر همان کار مثال بالا را با این روش انجام دهیم، می توانیم زمانی که transition را شروع می کنیم ، لیستی از همه x-offsets ها را برای scene جدید محاسبه کنیم، و آنها را به thread اصلی ارسال کنیم تا به صورت بهینه ای اجرا شوند . حالا که thread JavaScript از این مسئولیت آزاد شده، اگرهنگام render کردن scene چند frame از دست برود چندان مهم نیست - احتمالا حتی متوجه نخواهید شد چرا که توجه تان به transition است.

حل این مشکل، یکی از اهداف اصلی کتابخانه جدید React Navigation است. viewها در React Navigation از component های native و کتابخانه Animated برای ارائه انیمیشن های FPS 60 که در روی native thread اجرا می شوند، استفاده می کنند.

profiling عملکرد application

از profiler برای کسب اطلاعات دقیق در مورد کار انجام شده روی Thread JavaScript و Thread Main استفاده کنید. از طریق انتخاب Perf Monitor از منوی Debug می توان به آن دسترسی پیدا کرد.

برای iOS، Instruments ابزار ارزشمندی برای این منظور است و در android باید کار با systrace را یاد بگیرید.

اما ابتدا مطمئن شوید که Development Mode غیر فعال است! باید در log های application چنین چیزی ببینید:


__DEV__ === false, development-level warning are OFF, performance optimizations are ON

راه دیگری برای profiling جاوااسکریپت این است که حین debug از Chrome profiler استفاده کنید. این روش به شما نتایج دقیق نمی دهد چرا که کد در Chrome در حال اجرا است، اما به شما یک ایده کلی درمورد bottleneck های احتمالی میدهد. profiler را از تب Performance درChrome اجرا کنید. گرافی زیر User Timing ظاهر می شود. برای مشاهده جزئیات بیشتر در قالب جدول، رویtab Bottom Up کلیک کنید و سپس DedicatedWorker Thread را در منوی سمت چپ بالا انتخاب کنید.

پروفایلینگ performance رابط کاربری اندروید با systrace

android از بیش از ده هزار مویابل مختلف پشتیبانی می کند و برای پشتیبانی از render نرم افزار به کار می رود: معماری framework و نیاز به تعمیم بین سخت افزار های مختلف متاسفانه بدین معنی است که نسبت به iOS، برای چیزی ارزان تر، کمتر نسیبتان می شود! اما گاهی اوقات مواردی برای بهبود وجود دارد– و در موارد بسیاری مشکل اصلا از کد native نیست.

اولین گام برای debug این مشکل این است که به یک سوال اساسی پاسخ دهیم: در طول هر 16 میلی ثانیه frame کجا زمان از دست می رود؟ برای یافتن پاسخ این سوال، از یک ابزار استاندارد profiling android به نام systrace استفاده خواهیم کرد.

systrace یک ابزار استاندارد profiling برای android است (و با نصب Android platform-tools نصب می شود). بخش های کد توسط start/end marker های احاطه شده اند و سپس در قالب نمودار رنگی نمایش داده می شوند. هم Android SDK و هم framework React Native، marker های استانداردی دارند که توسط آن تصویر سازی کنید.

1. جمع آوری trace

ابتدا موبایلی که وقفه در آن اتفاق می افتد به وسیله ی USB به کامپیوتر خود متصل کنید و آن را در وضعیتی قرار دهید که می خواهید performance آن را profile کنید. systrace را به صورت زیر اجرا کنید:


$ < path_to_android_sdk >/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a < your_package_name >  

توضیحی مختصر درمورد این دستور:

time زمان جمع آوری دیتا به ثانیه است.

sched ، gfx و view تگ های SDK Android (مجموعه ای از marker ها) هستند که برای ما اهمیت دارند: sched اطلاعاتی در مورد آنچه که بر روی هر core موبایل اجرا می شود می دهد، gfx به شما اطلاعات گرافیکی مانند frame boundary ها می دهد و view اطلاعاتی درمورد اندازه ها، layout، و غیره می دهد.

-a < your_package_name > marker های خاص برنامه را فعال می کند، به ویژه آن هایی که در framework React Native ساخته شده اند. your_package_name را می توان در فایل AndroidManifest.xml application یافت و مثلا شبیه com.example.app است.

هنگامی که جمع آوری دیتا شروع شد، انیمیشن یا حرکت مورد نظرتان را انجام دهید. در پایان جمع آوری، systrace به شما یک link به دیتای جمع آوری شده می دهد که می توانید در browser خود باز کنید.

2. خواندن trace

پس از باز کردن trace در browser (ترجیحا chrome)، باید چیزی شبیه به این را ببینید:


خواندن trace

توجه: برای zoom و حرکت به راست و چپ از کلید های WASD استفاده کنید.

اگر فایل trace .html به درستی باز نمی شود، browser console را چک کنید. ممکن است چنین خطایی رخ داده باشد:


خواندن trace

از آنجا که Object.observe در browser های جدید منسوخ شده، باید فایل را با ابزار Google Chrome Tracing باز کنید:


  • در یک تب chrome://tracing را باز کنید.
  • Load را انتخاب کنید
  • فایل Html را انتخاب کنید

فعال کردن VSync highlighting

برای highlight کردن boundary های 16 میلی ثانیه ای frame، check box بالا سمت راست صفحه را انتخاب کنید.


VSync highlighting

باید خطوط به شکل عکس بالا مشخص باشد. اگر چنین خروجی نمی گیرید، روی دستگاه دیگری profiling را انجام دهید: گفته می شود Samsung در نمایش vsyncs مشکل دارد، سری های Nexus قابل اطمینان است.

3. Process مورد نظرتان را پیدا کنید

نام Package مورد نظرتان را بیابید. در این مثال من عملکرد com.facebook.adsmanager را profile می کردم، که به دلیل محدودیت book.adsmanager نمایش داده می شود.

در سمت چپ، می توانید مجموعه ای از thread ها را ببینید که مربوط به timeline row های سمت راست هستند. برای ما چند thread اهمیت دارد: UI thread (که هم نام با نام Package یا صرفا UI Thread هست)، mqt_js و mqt_native_modules. اگر android موبایل 5 به بالا باشد، Render Thread مهم می شود.

UI Thread: جایی که measure/layout/draw به طور استاندارد در android اتفاق می افتد. نام thread در سمت راست نام Package شما است یا صرفا UI Thread است. event هایی که روی این thread می بینید شبیه زیر است و با Choreographer ، traversals و DispatchUI سروکار دارد.


UI Thread

JS Thread: جایی که جاوااسکریپت اجرا می شود. نام این thread، بسته به اینکه device kernelچطور ساخته شده، mqt_js یا < ... > خواهد بود. برای تشخیص آن اگر نام ندارد، دنبال چیزهایی مثل JSCall و Bridge.executeJSCall و غیره بگردید.


JS Thread

Native Modules Thread. جایی که فراخوانی های ماژول های native(مثل UIManager) اتفاق می افتد. نام thread ، mqt_native_modules یا < ... > خواهد بود. برای تشخیص آن در حالت دوم، دنبال چیزهایی مثل NativeCall و callJavaModuleMethod و onBatchComplete بگردید:


Native Module Thread

Render Thread. اگر از android نسخه 5 به بالا استفاده می کنید، این thread را نیز در application خود دارید. این Thread دستورات OpenGL را تولید می کند و برای رسم UI است. نام آن RenderThread یا < ... > است. برای تشخیص آن در حالت دوم، دنبال چیزهایی مثل DrawFrame و queueBuffer بگردید.


Render Thread

تشخیص مشکل

یک انیمیشن روان باید شبیه موارد زیر باشد:


Smooth Animation

هر تغییر رنگ نشانگر یک frame است. به خاطر بیاورید برای نمایش یک frame، همه ی کارهای مربوط به UI باید در بازه ی 16 میلی ثانیه انجام شده باشد. توجه کنید هیچ thread نزدیک boundary frame کار نمی کند. یک Application که اینطور render شود در FPS 60، render می شود.

اگر در عملکرد اپلیکیشن وقفه ببینید، خروجی ها چیزی شبیه زیر است:


وقفه در عملکرد انیمیشن

توجه کنید JS thread اساسا تمام وقت و درطی boundary frame ها درحال اجراست. این application در 60 FPS، render نمی شود. در این مورد، مشکل از JS است.

ممکن است چیزی شبیه این ببینید:


وقفه در عملکرد انیمیشن

در این مورد، کارهای روی UI Thread و render thread از frame boundary گذشته است. UI که سعی می کنیم در هر frame، render کنیم کار زیادی می طلبد. در این مورد، مشکل از رندر view های native است.

تا اینجا، اطلاعات مفیدی دارید که برای گام های بعدی کمک می کند.

حل مشکلات جاواسکریپتی

اگر مشکل جاواسکریپتی را تشخیص دادید، در اسکریپتی که اجرا می کنید دنبال سرنخ بگردید. در سناریوی بالا، می بینیم RCTEventEmitter در هر frame چندبار فراخوانی می شود. شکل بزرگتر از JS thread را در زیر می بینید:


مشکل جاوااسکریپتی عملکرد React Native app

این درست به نظر نمی رسد. چرا چندبار فراخوانی می شود؟ آیا event های مختلفی هستند؟ پاسخ به این سوالات به کدتان بستگی دارد. و اکثر مواقع لازم است نگاهی به shouldComponentUpdate بیندازید.

حل مشکلات رابط کاربری native

اگر مشکل را از native UI می بینید، معمولا دو سناریو وجود دارد:


  • رابط کاربری که سعی می کنید در هر frame ترسیم کنید کار زیادی روی GPU می طلبد.
  • رابط کاربری جدیدی حین اجرای انیمیشن/تعاملات کاربر می سازید (مثلا حین scroll محتوای جدید load می کنید).

کار زیاد GPU

در سناریوی اول، می بینید UI thread یا Render thread چیزی شبیه زیر است:


کار زیاد GPU

توجه کنید چقدر زمان در DrawFrame صرف می شود که از boundary frame ها رد شده است. این زمان صرف شده منتظر GPU بوده تا command buffer خود را از frame قبلی پاک کند.

برای بهبود این وضعیت باید:


  • استفاده از renderToHardwareTextureAndroid را برای محتواهای پیچیده و ثابت که animate شده (به عنوان مثال، Navigator slide/alpha animation) را بررسی کنید.
  • اطمینان حاصل کنید که از needsOffscreenAlphaCompositing استفاده نمی کنید، که به طور پیش فرض غیر فعال است، چرا که سربار هر frame بر روی GPU به میزان قابل توجهی افزایش می یابد.

برای اینکه عمیق تر در مورد آنچه GPU در واقع انجام می دهد بدانید، می توانید از Tracer for OpenGL ES استفاده کنید.

ایجاد viewهای های جدید روی UI thread

در سناریوی دوم، چیزی شبیه به این خواهید یافت:


پیمایش سنگین روی UI Thread

توجه داشته باشید که thread JS اول وقفه ای دارد، سپس می بینید کارهایی روی native modules thread انجام شده و به دنبال آن پیمایش سنگینی روی UI thread انجام شده.

هیچ راه آسانی برای حل این مشکل وجود ندارد مگر آنکه بتوانید ایجاد یک UI جدید را تا پس از تعامل ، به تاخیر بیاندازید یا اینکه UI را ساده کنید. ما در حال کار بر روی یک راه حل زیرساختی برای این مشکل هستیم، که به UI های جدید اجازه می دهد که از خارج از thread main ایجاد شده و config شوند، و اجازه می دهد تعامل بدون مشکل ادامه یابد.

RAM bundles + inline requires

اگر application بزرگی دارید، ممکن است بخواهید RAM format bundle و inline require ها استفاده کنید. این کار برای برنامه هایی مناسب است که دارای تعداد زیادی صفحه هستند که ممکن است اصلا در حین استفاده معمول از برنامه باز نشوند. به طور کلی برای برنامه هایی مناسب است که دارای مقادیر زیادی کد هستند که تا زمانی پس از راه اندازی اولیه application لازم نیستند. به عنوان مثال این برنامه شامل صفحه های profile پیچیده و یا قابلیت هایی است که کمتر استفاده می شود، اما اغلب کارها فقط شامل بازدید از صفحه اصلی برنامه است. می توانیم بارگذاری bundle را با استفاده از RAM format بهینه سازی کنیم و این قابلیت ها و صفحه های متعدد را به صورت inline، require کنیم (زمانی که واقعا استفاده می شوند).

load جاوااسکریپت

قبل از اینکه React Native بتواند کد JS را اجرا کند، کد باید درون حافظه load و parse شود. برای یک bundle استاندارد، اگر bundle 50 مگابایتی را load کنید، قبل اجرا، باید کل 50 مگابایت load و parse شود. بهینه سازی درRAM bundle ها این گونه است که شما می توانید تنها بخشی از 50mb که حقیقتا در هنگام راه اندازی نیاز دارید load کنید و بقیه را دربخش هایی که موردنیاز هستند، load کنید.

Inline Requires

Inline Require کردن یک ماژول یا فایل، require کردن آن را تا زمان نیاز به تعویق می اندازد. برای مثال:


VeryExpensive.js
import React, { Component } from 'react';
import { Text } from 'react-native';
// ... import some very expensive modules
// You may want to log at the file level to verify when this is happening
console.log('VeryExpensive component loaded');
export default class VeryExpensive extends Component {
  // lots and lots of code
  render() {
    return < Text >Very Expensive Component< /Text >;
  }
}


Optimized.js
import React, { Component } from 'react';
import { TouchableOpacity, View, Text } from 'react-native';
 
let VeryExpensive = null;
 
export default class Optimized extends Component {
  state = { needsExpensive: false };
 
  didPress = () = > {
    if (VeryExpensive == null) {
      VeryExpensive = require('./VeryExpensive').default;
    }
 
    this.setState(() = > ({
      needsExpensive: true,
    }));
  };
 
  render() {
    return (
						< View style={ { marginTop: 20 } }>
                        < touchableopacity onpress="{ this.didPress }">
                        < text >Load < /text>
        < /touchableopacity >
        { this.state.needsExpensive ? < VeryExpensive /> : null}
      < /view>
    );
  }
}

حتی بدون RAM format، Inline Require ها می توانند زمان راه اندازی برنامه را کم کنند، چرا که کد درون VeryExpensive.js فقط زمانی اجرا می شود که اولین بار به آن نیاز است و require می شود.

فعال کردن RAM format

در iOS استفاده از RAM format، یک فایل Index شده می سازد که React Native هربار یک ماژول آن را load می کند. در Android، به طور پیش فرض مجموعه ای از فایل ها برای هر ماژول ایجاد می کند. شما می توانید android را مجبور کنید مانند iOS یک فایل واحد بسازد، اما با استفاده از چندین فایل performance بهتر و مصرف memory کمتر می شود.

RAM format را در Xcode ، از طریق ویرایش مرحله build "Bundle React Native code and images" فعال کنید. قبل از ../node_modules/react-native/scripts/react-native-xcode.sh و export BUNDLE_COMMAND="ram-bundle":


export BUNDLE_COMMAND="ram-bundle"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh

در اندروید، RAM format را از طریق ویرایش فایل android/app/build.gradle فعال کنید. قبل از خط apply from: "../../node_modules/react-native/react.gradle"، بلوک project.ext.react را اضافه کنید:


project.ext.react = [
  bundleCommand: "ram-bundle",
]

در android برای داشتن یک فایل به جای چند فایل، از کد زیر استفاده کنید:


project.ext.react = [
  bundleCommand: "ram-bundle",
  extraPackagerArgs: ["--indexed-ram-bundle"]
]

تنظیمات preloading و inline require ها

حال که از RAM bundle استفاده می کنیم، سرباری اضافی برای صدا زدن require هست. Require وقتی با ماژولی که هنوز load نشده مواجه می شود باید پیامی روی bridge بفرستد. این بیش از همه، روی راه اندازی اولیه تاثیر می گذارد، چرا که درهنگام بارگذاری اولین ماژول ها بیشترین تعداد فراخوانی require را داریم. خوشبختانه می توانیم بخشی از ماژول ها را طوری config کنیم که از پیش load شوند. برای این کار باید یک سری inline require پیاده سازی کنید.

بررسی ماژول های load شده

در فایل root، (index.(ios|android).js) خطوط زیر را پس از import های اولیه، اضافه کنید:


const modules = require.getModules();
const moduleIds = Object.keys(modules);
const loadedModuleNames = moduleIds
  .filter(moduleId = > modules[moduleId].isInitialized)
  .map(moduleId = > modules[moduleId].verboseName);
const waitingModuleNames = moduleIds
  .filter(moduleId = > !modules[moduleId].isInitialized)
  .map(moduleId = > modules[moduleId].verboseName);
// make sure that the modules you expect to be waiting are actually waiting
console.log(
  'loaded:',
  loadedModuleNames.length,
  'waiting:',
  waitingModuleNames.length
);
// grab this text blob, and put it in a file named packager/modulePaths.js
console.log(`module.exports = $ {JSON.stringify(loadedModuleNames.sort())};`);

وقتی application را اجرا می کنید، console را نگاه کنید و تعداد ماژول های load شده، و ماژول های waiting را ببینید. می توانید moduleNames ها را بخوانید. دقت کنید inline require ها وقتی فراخوانی می شوند که برای اولین بار به import ها reference داده شود. می توانید از object Systrace روی require را برای debug آن ها به شکلی که می خواهید استفاده کنید.


require.Systrace.beginEvent = (message) = > {
  if(message.includes(problematicModule)) {
    throw new Error();
  }
}

هر application متفاوت است، اما عمدتا منطقی است اگر فقط ماژول هایی را load کنید که برای صفحه ی اول به آن ها نیاز دارید. پس از اتمام کار، خروجی loadedModuleNames را درون فایل packager/modulePaths.js بگذارید.

ویرایش فایل metro.config.js

حال باید فایل metro.config.js در root پروژه را طوری ویرایش کنیم که از فایل تازه ایجاد شده ی modulePaths.js استفاده کند:


const modulePaths = require('./packager/modulePaths');
const resolve = require('path').resolve;
const fs = require('fs');
// Update the following line if the root folder of your app is somewhere else.
const ROOT_FOLDER = resolve(__dirname, '..');
const config = {
  transformer: {
    getTransformOptions: () = > {
      const moduleMap = {};
      modulePaths.forEach(path = > {
        if (fs.existsSync(path)) {
          moduleMap[resolve(path)] = true;
        }
      });
      return {
        preloadedModules: moduleMap,
        transform: { inlineRequires: { blacklist: moduleMap } },
      };
    },
  },
  projectRoot:ROOT_FOLDER,
};
module.exports = config;

ورودی preloadedModules در config مشخص می کند که کدام ماژول باید هنگام build یک RAM bundle باید برای pre-load شدن، نشانه گذاری شود. هنگامی که bundle بارگذاری می شود، این ماژول ها بلافاصله load می شوند، قبل از اینکه حتی یک require اجرا شود. ورودی blacklist مشخص می کند که این ماژول ها به صورت خطی require شوند. از آنجا که pre-load می شوند، هیچ مزیتی از حیث performance در استفاده از inline require وجود ندارد. در اصل جاوا اسکریپت هربار که به import ها reference داده می شود، زمان اضافی صرف آماده سازی inline require ها می کند.

تست و بررسی بهینه سازی ها

اکنون آماده استفاده از RAM format و inline require ها هستید. اطمینان حاصل کنید startup time قبل و بعد را اندازه گیری کنید.

  • 467
  •    0
  • تاریخ ارسال :   1398/06/04

دانشجویان گرامی اگر این مطلب برای شما مفید بود لطفا ما را در GooglePlus محبوب کنید
رمز عبور: tahlildadeh.com یا www.tahlildadeh.com
ارسال دیدگاه نظرات کاربران
شماره موبایل دیدگاه
عنوان پست الکترونیک

ارسال

آموزشگاه برنامه نویسی تحلیل داده
آموزشگاه برنامه نویسی تحلیل داده

تمامی حقوق این سایت متعلق به آموزشگاه تحلیل داده می باشد .