این مطلب پنجمین مطلب از مجموعه «تجربه استفاده از React Native در Airbnb» است که ما در آن به بررسی تجربه استفاده از ریاکت نیتیو و آینده اپلیکیشن موبایلی در Airbnb میپردازیم.
زمان هیجان انگیز پیش رو
حتی زمانی که مشغول توسعه اپلیکیشن با استفاده از ریاکت نیتیو بودیم، همزمان روی کدهای نیتیو نیز کار میکردیم. در حال حاضر ما پروژههایی را در چرخه تولید در اختیار داریم. بخش جالب ماجرا اینجاست که روند الهام گرفتن این پروژهها در فرایند تجربه کاری با ریاکت نیتیو و یادگیری آن اتفاق افتاد.
رندرینگ مبتنی بر سرور
اگرچه ما از ریاکت نیتیو دیگر استفاده نمیکنیم اما هنوز ارزش آن در نوشتن یکبار کد برای پلتفرمهای مختلف را در نظر داریم. در حال حاضر نیز هنوز از سیستم زبان طراحی DLS استفاده میکنیم و توانستهایم که بسیاری از اسکرینها را مشابه یکدیگر در بیاوریم.
چندین تیم مختلف در پروسه توسعه اپلیکیشنها از چهارچوبهای رندرینگ مبتنی بر سرور استفاده میکنند. با استفاده از این چهارچوبها، سرور دادههایی را برای تنظیم شیوه رندر کامپوننتها، پیکربندی اسکرین و تعاملات ارسال میکند. هرکدام از پلتفرمهای موبایل این دادهها را تفسیر کرده و بعد به صورت محلی در اسکرینها رندر میکنند و یا اینکه میتوانند به صورت کامل نیز از ساختار DLS استفاده کنند.
رندرینگ مبتنی بر سرور با چالشها و مشکلات خود همراه است. در زیر میتوانید مواردی را مشاهده کنید که در رفع آنها کمک کردهایم:
- بروزرسانی امن کامپوننتها و همزمان نگهداری از سازگاری با قسمت Back-End.
- به اشتراک گذاری تعریف نوعی برای کامپوننتها در سراسر پلتفرمها.
- پاسخگویی به رویدادها در زمان اجرا، برای مثال کلیک روی یک دکمه و... .
- انتقال بین چندین اسکرین مبتنی بر جیسان و همزمان آماده کردن وضعیت داخلی.
- رندر کامل کامپوننتهای سفارشی. مواردی که در زمان ایجاد پیادهسازی نشدهاند.
کامپوننتهای Epoxy
در سال ۲۰۱۶ ما Epoxy نسخه آندروید را به صورت متن باز در اختیار عموم قرار دادیم. Epoxy یک فریمورک است که قابلیت ایجاد RecyclerViews، UICollectionViews و UITableViews ناهمگون را فراهم میسازد. امروزه بیشتر اسکرینها از Epoxy استفاده میکنند. انجام چنین کاری به ما این اجازه را میدهد تا هر اسکرین را به کامپوننتهای ایزوله شدهای تبدیل کنیم و به Lazy-Rendering برسیم. امروز ما Epoxy را روی آندروید و IOS در اختیار داریم.
برای آیاواس ما حالتی مانند زیر را داریم:
BasicRow.epoxyModel(
content: BasicRow.Content(
titleText: "Settings",
subtitleText: "Optional subtitle"),
style: .standard,
dataID: "settings",
selectionHandler: { [weak self] _, _, _ in
self?.navigate(to: .settings)
})
در آندروید ما میتوانیم از قدرت Kotlin برای انجام چنین کاری استفاده کنیم:
basicRow {
id("settings")
title(R.string.settings)
subtitleText(R.string.settings_subtitle)
onClickListener { navigateTo(SETTINGS) }
}
تقسیم Epoxy
در ریاکت شما یک لیست از کامپوننتها را از رندر برگشت میدهید. کلید کارایی در ریاکت این است که آن کامپوننتها تنها یک مدل دادهای هستند که views/HTML واقعی که برای رندر میخواهید را نمایش میدهند. بعد از آن درخت کامپوننت تقسیم شده و تنها تغییرات ارسال میشود. ما چنین مفهومی را برای Epoxy نیز فراهم کردیم. در Epoxy شما مدلها را برای اسکرین کلیتان در buildModels ایجاد میکنید. چنین موردی در کاتولین به صورت زیر پیادهسازی میشود:
override fun EpoxyController.buildModels() {
header {
id("marquee")
title(R.string.edit_profile)
}
inputRow {
id("first name")
title(R.string.first_name)
text(firstName)
onChange {
firstName = it
requestModelBuild()
}
}
// Put the rest of your models here...
}
هر زمانی که دادههای شما تغییر میکند، میتوانید requestModelBuild() را فراخوانی کنید و این قسمت اسکرین شما را از طریقRecyclerView همراه با تغییرات دوباره رندر میکند.
در آیاواس این موضوع شبیه زیر است:
override func itemModel(forDataID dataID: DemoDataID) -> EpoxyableModel? {
switch dataID {
case .header:
return DocumentMarquee.epoxyModel(
content: DocumentMarquee.Content(titleText: "Edit Profile"),
style: .standard,
dataID: DemoDataID.header)
case .inputRow:
return InputRow.epoxyModel(
content: InputRow.Content(
titleText: "First name",
inputText: firstName)
style: .standard,
dataID: DemoDataID.inputRow,
behaviorSetter: { [weak self] view, content, dataID in
view.textDidChangeBlock = { _, inputText in
self?.firstName = inputText
self?.rebuildItemModel(forDataID: .inputRow)
}
})
}
}
فریمورک محصول آندروید (MvRx)
یکی از مهیجترین محصولاتی که به تازگی توسعه دادهایم فریمورکی است که با نام داخلی MvRx آن را استفاده میکنیم. این فریمورک ترکیبی از Epoxy, Jetpack, RxJava و کاتولین است که در آن از قواعد مبتنی بر ریاکت برای ساخت اسکرینهای جدید استفاده شده است. تا به اینجای کار این فریمورک روی اسکرینهای بسیاری به خوبی امتحان شده است. از این به بعد نیز نیازی به کار کردن با lifecycles ندارید.
کدهای زیر باعث ایجاد اسکرینی میشود که توانایی ارسال درخواست از طریق شبکه را دارد:
data class SimpleDemoState(val listing: Async<Listing> = Uninitialized)
class SimpleDemoViewModel(override val initialState: SimpleDemoState) : MvRxViewModel<SimpleDemoState>() {
init {
fetchListing()
}
private fun fetchListing() {
// This automatically fires off a request and maps its response to Async<Listing>
// which is a sealed class and can be: Unitialized, Loading, Success, and Fail.
// No need for separate success and failure handlers!
// This request is also lifecycle-aware. It will survive configuration changes and
// will never be delivered after onStop.
ListingRequest.forListingId(12345L).execute { copy(listing = it) }
}
}
class SimpleDemoFragment : MvRxFragment() {
// This will automatically subscribe to the ViewModel state and rebuild the epoxy models
// any time anything changes. Similar to how React's render method runs for every change of
// props or state.
private val viewModel by fragmentViewModel(SimpleDemoViewModel::class)
override fun EpoxyController.buildModels() {
val (state) = withState(viewModel)
if (state.listing is Loading) {
loader()
return
}
// These Epoxy models are not the views themself so calling buildModels is cheap. RecyclerView
// diffing will be automaticaly done and only the models that changed will re-render.
documentMarquee {
title(state.listing().name)
}
// Put the rest of your Epoxy models here...
}
override fun EpoxyController.buildFooter() = fixedActionFooter {
val (state) = withState(viewModel)
buttonLoading(state is Loading)
buttonText(state.listing().price)
buttonOnClickListener { _ -> }
}
}
MvRx ساختار بسیار سادهای برای مدیریت Fragment argها، savedInstanceState، TTI tracking و تعدادی ویژگی دیگر دارد.
در حال حاضر روی فریمورکی مشابه با این مورد برای IOS نیز کار میکنیم.
سرعت چرخش
یکی از چیزهایی که در زمان برگشت از ریاکت نیتیو به سوی پلتفرم نیتیو بسیار واضح بود، سرعت چرخش بود. رفتن از سیستمی که تست کردن در آن تنها چند ثانیه طول میکشد به سیستمی که این مدت زمان به دقایق میرسد غیر قابل قبول است. اما خوشبختانه ما کمکهایی را نیز برای خودمان در این مسیر ایجاد کردیم.
ما زیرساختی را روی آندروید و آیاواس ایجاد کردیم که قادر است تا تنها بخشی از اپلیکیشن را که شامل لانچر و موارد ضروری میشود کامپایل کند.
در آندروید، برای این مورد از gradle product flavors استفاده میشود. ماژول gradle ما به صورت زیر است:
این سطح جدید از درجهبندی مهندسین را قادر میسازد تا بتوانند براساس تنها مقداری از اپلیکیشن فرایند ایجاد یا build را انجام دهند. استفاده از مدل IntelliJ module unloading میتوانید در بهبود وضعیت build و کارایی IDE در مکبوک پرو تاثیر بسیار خوبی بگذارد.
استفاده کردن از این شیوه برای IOS به صورت زیر خواهد بود:
چنین سیستمی در فرایند build ۳ تا ۸ برابر سریعتر است.
در پایان
اینکه شرکتی باشید که از امتحان کردن تکنولوژیهای جدید نمی ترسید و همزمان میتوانید کیفیت، سرعت و تجربه توسعهدهنده خوبی داشته باشید و آن را حفظ کنید عالی است. در نهایت ریاکت نیتیو توانست به ما ثابت کند که میتواند ابزار لازم و اصلی برای توسعه ویژگیها باشد همچنین به ما این کمک را کرد تا بتوانیم به شیوهای جدید از توسعه موبایل فکر کنیم.
این مطلب پنجمین مطلب از مجموعه «تجربه استفاده از React Native در Airbnb» است که ما در آن به بررسی تجربه استفاده از ریاکت نیتیو و آینده اپلیکیشن موبایلی در Airbnb میپردازیم.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید