دوره‌ای که می‌تونه مسیر شغلیت رو عوض کنه! دوره‌ای که می‌تونه مسیر شغلیت رو عوض کنه!
🎯 ثبت نام

آموزش ارتباط بین React Native و پلتفرم native

آموزش ارتباط بین React Native و پلتفرم native

در بخش ادغام React Native با application های موجود و بخش کامپوننت های native برای UI، درمورد نحوه ی ادغام React Native درون component های native و برعکس صحبت کردیم. هنگام ادغام این دو، نیاز به راهی برای ارتباط بین آن ها هستیم. به بعضی روش ها قبلا اشاره کرده ایم. این بخش خلاصه ای درمورد روش های موجود است.

مقدمه

React Native از الهام گرفته است، ایده اصلی جریان اطلاعات در آن ها مشابه است. در React جریان یک سویه است. سلسله مراتبی از Component React ها داریم و هر component فقط به parent خود و وضعیت داخلی اش وابسته است. این کار را با property ها انجام می دهیم: دیتا از parent به child منتقل می شود. اگر یک component در رده ی بالا به وضعیت یکی از childهایش وابسته باشد، باید callback به آن بفرستد و از آن callback برای update کردنش استفاده شود.

همین مفهوم برای React Native هم صادق است. تا وقتی که application کامل با استفاده از framework توسعه داده شود، با property ها و callback ها کار می کند. اما وقتی آن را با component native ها ادغام می کنیم به یک مکانیزم بین زبانی خاص برای ارسال اطلاعات بین آن ها احتیاج داریم.

property ها

property ها ساده ترین راه ارتباط بین component هاست. باید راهی برای ارسال property از native به React Native و برعکس باشد.

ارسال property ها از native به React Native

برای جای دادن یک React Native view درون یک native component ، از RCTRootView استفاده می کنیم. RCTRootView یک UIView است که یک React Native application را نگه می دارد. بعلاوه واسطی بین native و application هاست شده است.

RCTRootView یک " initializer" دارد که به شما امکان ارسال property های دلخواه به application React Native می دهد. پارامتر initialProperties باید یک object از NSDictionary باشد. دیکشنری به object JSON تبدیل می شود که component رده بالای جاوااسکریپت می تواند به آن reference دهد.


1
2
3
4
5
6
NSArray *imageList = @[@"http://foo.com/bar1.png",
                       @"http://foo.com/bar2.png"];
NSDictionary *props = @{@"images" : imageList};
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                 moduleName:@"ImageBrowserApp"
                                          initialProperties:props];<button></button>


1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import { AppRegistry, View, Image } from 'react-native';
class ImageBrowserApp extends React.Component {
  renderImage(imgURI) {
    return < Image source = { { uri: imgURI } } />;
  }
  render() {
    return < View >{ this.props.images.map(this.renderImage) }< /View>;
  }
}
AppRegistry.registerComponent('ImageBrowserApp', () = > ImageBrowserApp);<button></button>

هرزمان بخواهید می توانید property ها را update کنید. با این حال، update ها باید روی thread اصلی انجام شود. می توانید getter را روی هر thread استفاده کنید.

توجه: درحال حاضر، مشکلی برای مقداردهی به appProperties در هنگام راه اندازی bridge وجود دارد. تغییرات از دست می رود. برای اطلاعات بیشتر این لینک را ببینید.

راهی برای update کردن فقط بعضی property ها در هر لحظه وجود ندارد. توصیه می کنیم آن را در wrapper خود پیاده سازی کنید.

توجه: درحال حاضر، تابع جاوااسکریپتی componentWillUpdateProps از component رده بالای RN پس از update شدن یک prop فراخوانی نمی شود. می توانید به prop های جدید با تابع componentDidMount دسترسی پیدا کنید.

ارسال property ها از React Native به کد native

مشکل در دسترس قرار دادن property های یک native component به طور کامل در این بخش قابل مطالعه است. به طور خلاصه، property ها را توسط ماکروی RCT_CUSTOM_VIEW_PROPERTY در native component سفارشی خودتان قابل دسترس قرار دهید، سپس component را طوری که انگار یک React Native component معمولی است استفاده کنید.

محدودیت های property ها

اشکال اصلی property های بین زبان ها این است که callback ها را ، که به ما اجازه می دهد تا از ارتباط داده ها از پایین به بالا استفاده کنیم، پشتیبانی نمی کنند. تصور کنید که یکview RN کوچک دارید که می خواهید از view parent native به عنوان نتیجه یک action جاوااسکریپتی حذف شود. هیچ راهی برای انجام آن با props وجود ندارد، زیرا لازمه ی آن ارسال دیتا از پایین به بالاست.

اگر چه callback های بین زبانی را تا حدی داریم، (در اینجا توضیح داده شده است)، این callback ها همیشه چیزی نیست که ما نیاز داریم. مشکل اصلی این است که آنها به منظور انتقال به شکل property نیستند. درواقع، این مکانیزم ما را قادر می سازد تا یک action native را از JS فراخوانی کنیم و نتیجه آن action را در JS مدیریت کنیم.

دیگر راه های تعامل بین زبان ها (eventها و ماژول های native)

همان طور که در قسمت قبل گفته شد، استفاده از property ها محدودیت های زیادی دارد. گاهی property ها برای اعمال منطق application کافی نیستند و راه حل انعطاف پذیرتری نیاز داریم. این بخش سایر راه های ارتباطی موجود در React Native را ، که برای ارتباطات داخلی (بین لایه های JS و native در RN) و خارجی (بین RN و قسمت های کاملا native application) استفاده می شود، توضیح می دهد.

React Native به شما امکان فراخوانی توابع بین زبان ها را می دهد. می توانید کد native سفارشی خود را از جاوااسکریپت و برعکس اجرا کنید. متاسفانه، برحسب اینکه کدام سمت باشیم روش ها متفاوت است. برای native از مکانیزم event برای زمان بندی اجرای یک handler در JS استفاده می کنیم ، در حالی که برای React Native متدهای در دسترس توسط ماژول های native را مستقیما صدا می زنیم.

فراخوانی توابع React Native از سمت پلتفرم native (eventها)

eventها به طور مفصل در بخش component های native برای iOS، بیان شده اند. دقت کنید استفاده از eventها تضمیمی روی زمان اجرای آن ها نمی دهد، چرا که event روی یک thread جدا مدیریت می شود.

eventها قدرتمند هستند، چرا که تغییر کامپوننت های React Native را بدون نیاز به reference دادن به آن را، ممکن می کند. با این حال کار با آن ها مشکلاتی هم دارد:


  • از آنجا که eventها از هرجایی می توانند فراخوانی شوند، dependency های بی قانون و قاعده ای به پروژه اضافه می کنند.
  • eventها namespace را به اشتراک می گذارند. ممکن است مغایرت هایی بین نام ها پیش آید. این مغایرت ها را نمی تواند استاتیک شناسایی کرد و این موضوع debug کردنشان را سخت می کند.
  • اگر از چند نمونه از یک component React Native استفاده کنید، باید از شناسه ای برای متمایز کردنشان از نظر eventها استفاده کنید .. (می توانید از reactTag به عنوان شناسه استفاده کنید)

الگوی رایج هنگام ادغام کد native در React Native، این است که کامپوننت native RCTViewManager را یک delegate برای view ها کنیم، و توسط bridge، event ها را به جاوااسکریپت بفرستیم. این کار فراخوانی های event های مربوط به هم را در یک جا نگه می دارد.

فراخوانی توابع native از React Native (ماژول های native)

ماژول های native در واقع کلاس های Objective-C هستند که در جاوااسکریپت قابل دسترس اند. عموما به ازای JS bridg یک نمونه از هر ماژول ساخته می شود. می توانند توابع و ثوابت دلخواهی را در دسترس React Native قرار دهند . این موضوع در مقاله ماژول های Native برای iOS، کامل بحث شده است.

این حقیقت که ماژول های native ، singleton هستند مکانیزم ادغام کردن را محدود می کند. فرض کنید یک کامپوننت React Native را درون یک view native قرار داده ایم و می خواهیم view native parent آن را آپدیت کنیم. با استفاده از مکانیزم ماژول native، متدی در دسترس JS قرار می دهیم که نه تنها آرگومان های مورد نظر را می گیرد، که شناسه ی view native parent را نیز دریافت می کند. شناسه برای بازیابی reference به ویوی parent استفاده می شود. پس به ذخیره کردن نگاشتی از شناسه ها به ویوهای native در ماژول احتیاج داریم.

این راه حل پیچیده است، اما در RCTUIManager که کلاسی داخلی در React Native است که همه ی view ها را مدیریت می کند، استفاده شده است.

ماژول های native برای در دسترس قرار دادن کتابخانه های native موجود برای JS نیز استفاده می شود. کتابخانه ی Geolocation یک مثال برای این منظور است.

هشدار: همه ی ماژول های native یک namespace مشترک دارند. مراقب تداخلات نام آن ها باشید.

Layout calculation flow

هنگام ادغام React Native و کد native، به راهی برای یکی کردن دو سیستم متفاوت layout ها نیاز داریم. این بخش مشکلات رایج layout را بررسی می کند و به طور خلاصه راه حل هایی برای آن ها ارائه می دهد.

layout یک native component جاسازی شده در React Native

این مورد در بخش استایل ها در component های native برای iOS بررسی شده. اساسا، از آنجا که تمام viewهای native React زیرکلاس هایی از UIView هستند، attribute های مربوط به اندازه و استایل به شکلی که مورد انتظار ماست عمل خواهند کرد.

layout یک کامپوننت React Native جاسازی شده در native

Content React Native با اندازه ثابت

ساده ترین سناریو وقتی است که یک application React Native با اندازه ثابت داریم، که کد سمت native این را می داند. مثلا یک View React Native تمام صفحه این گونه است. اگر یک root view کوچکتر بخواهیم، می توانیم فریم RCTRootView را صریح مقداردهی کنیم.

برای مثال، برای اینکه یک RN app ارتفاع 200 پیسکل بگیرد، عرض view که روی آن قرار گرفته را بگیرد، باید:


1
2
3
4
5
6
7
8
9
10
// SomeViewController.m
- (void)viewDidLoad
{
  [...]
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:appName
                                            initialProperties:props];
  rootView.frame = CGRectMake(0, 0, self.view.width, 200);
  [self.view addSubview:rootView];
}<button></button>

وقتی یک root view با اندازه ی ثابت داریم، باید مرزهایش سمت JS را رعایت کنیم. به بیان دیگر، باید مطمئن شویم Content React Native حتما درون root view با اندازه ی ثابت جای می گیرد. آسان ترین راه، استفاده از flexbox layout است. اگر مقادیر absolute برای position استفاده کنید، و کامپوننت React بیرون از مرزهای root view قابل دیدن باشد، با view های native تداخل پیدا می کند و بعضی قابلیت ها به درستی کار نمی کنند. برای مثال، 'TouchableHighlight' ، touch های شما را بیرون از مرزهای root view مشخص نمی کند.

اگر اندازه ی root view را، با دوباره مقداردهی کردن به property frame، داینامیک آپدیت کنیم مشکلی نیست. React Native به Layout Content رسیدگی می کند.

محتوای React Native با اندازه متغیر

در بعضی موارد می خواهیم محتوایی که در ابتدا سایز مشخصی ندارد render کنیم. فرضا اندازه آن به صورت داینامیک در JS تعیین می شود. دو راه حل برای این مسئله داریم:


  • می توانید veiw React Native خود را درون یک کامپوننت ScrollView قرار دهید. این تضمین می کند که محتوای آن همیشه در دسترس باشد و با view های native دیگر تداخل پیدا نکند.
  • React Native این امکان را می دهد در جاوااسکریپت سایز RN app را تعیین کنیم و آن را به دارنده ی RCTRootView بدهیم. دارنده مسئول تغییر layout subview ها و مدیریت UI است. با حالت های مختلف flexibility برای RCTRootView می توان این کار را انجام داد.

RCTRootView چهار حالت flexibility را پشتیبانی می کند:


1
2
3
4
5
6
7
8
// RCTRootView.h
typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) {
  RCTRootViewSizeFlexibilityNone = 0,
  RCTRootViewSizeFlexibilityWidth,
  RCTRootViewSizeFlexibilityHeight,
  RCTRootViewSizeFlexibilityWidthAndHeight,
};
<button></button>

RCTRootViewSizeFlexibilityNone مقدار پیش فرض است، که اندازه ی root view را ثابت می کند (اما می توان آن را با setFrame: تغییر داد). سه حالت دیگر به ما امکان دنبال کردن تغییرات اندازه ی content React Native را می دهد. برای مثال، تنظیم حالت به RCTRootViewSizeFlexibilityHeight باعث می شود React Native، content height را اندازه بگیرد و آن را به delegate RCTRootView ارسال کند. هر action دلخواهی در این delegate قابل پیاده سازی است، مثلا مقداردهی به root view frame، تا محتوا درست جا بگیرد. delegate فقط زمانی فراخوانی می شود که سایز محتوا تغییر کند.

هشدار: flexible کردن ابعاد در جاوااسکریچت و native ممکن است رفتاری غیرقابل پیشبینی داشته باشد. برای مثال، وقتی از RCTRootViewSizeFlexibilityWidth روی RCTRootView استفاده کرده اید، width یک کامپوننت رده بالای React Native را flexible نکنید (با flexbox).

مثال:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// FlexibleSizeExampleView.m
- (instancetype)initWithFrame:(CGRect)frame
{
  [...]
  _rootView = [[RCTRootView alloc] initWithBridge:bridge
  moduleName:@"FlexibilityExampleApp"
  initialProperties:@{}];
  _rootView.delegate = self;
  _rootView.sizeFlexibility = RCTRootViewSizeFlexibilityHeight;
  _rootView.frame = CGRectMake(0, 0, self.frame.size.width, 0);
}
#pragma mark - RCTRootViewDelegate
- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView
{
  CGRect newFrame = rootView.frame;
  newFrame.size = rootView.intrinsicContentSize;
  rootView.frame = newFrame;
}<button></button>

در این مثال، یک FlexibleSizeExampleView داریم که یک root view را نگه می دارد. یک root view می سازیم، آن را مقداردهی اولیه می کنیم، و delegate را ست می کنیم. delegate تغییرات اندازه را مدیریت می کند. سپس flexibility مربوط به اندازه ی root view را به RCTRootViewSizeFlexibilityHeight مقداردهی می کنیم، که به این معناست که متد rootViewDidChangeIntrinsicSize: هربار که ارتفاع محتوای React Native تغییر کند، فراخوانی می شود. در اخر، width و position مربوط به root view را مقداردهی می کنیم. توجه کنید یک height هم می دهیم، اما تاثیری نخواهد داشت چرا که height را RN-dependent می کنیم.

کد کامل این مثال را می توانید در این لینک ببینید.

می توانید حالت flexibility را به طور داینامیک تغییر دهید. تغییر آن برای root view باعث تعیین مجدد layout می شود، و متد rootViewDidChangeIntrinsicSize: وقتی اندازه ی محتوا مشخص شد، فراخوانی می شود.

نکته: محاسبات مربوط به layout در React Native روی یک thread خاص اجرا می شود، حال آنکه بروزرسانی view های native روی thread اصلی انجام می شود. این ممکن است ناسازگاری هایی گذرا بین React Native و native بوجود آورد. این مورد مشکلی شناخته شده است و تیم ما درحال حاضر روی حل آن کار می کند.

نکته: React Native تا زمانی که root view زیرمجموعه ی view دیگری نشود محاسبات مربوط به layout را شروع نمی کند. اگر می خواهید view را تا زمان تعیین ابعادش مخفی کنید، root view را زیرمجموعه کنید و آن را از ابتدا مخفی کنید (با استفاده از property hidden در UIView). سپس visibility آن را درون delegate تغییر دهید.

1398/06/10 1846 0
نظرات شما

نظرات خود را ثبت کنید...