本文翻譯自What’s Next for Mobile at Airbnb
這是我們關(guān)于React Native的系列文章的第五篇,描述我們在
React Native上的經(jīng)驗和我們下一步在Airbnb移動端上的工作。
Exciting Times Ahead
在嘗試React Native時,我們也同時加快在原生上的努力。今天,我們在產(chǎn)品中有了大量令人驚艷的項目。這些項目的靈感來自于我們在學(xué)習(xí)React Native時的經(jīng)驗。
服務(wù)驅(qū)動渲染(Server-Driven Rendering)
雖然我們不再使用React Native,但我們?nèi)匀豢吹搅酥粚懸环莓a(chǎn)品代碼的價值。我們?nèi)匀皇忠蕾囄覀兘y(tǒng)一的設(shè)計語言系統(tǒng)(design language system , DLS),很多界面在 Android 和 iOS 中看起來是一樣的。
一些團隊嘗試并開始為強大的server-driven rendering框架達成一致。在這些框架中,server發(fā)送數(shù)據(jù)到設(shè)備來描述渲染的組件、界面配置和發(fā)生的交互行為。不同移動平臺解析數(shù)據(jù)并渲染原生界面或者完全使用DLS的組件。
大量使用Server-driven rendering帶來了一些挑戰(zhàn),這里是我們的一些解決方案:
- 安全更新組件定義的同時保持向后兼容
- 對于跨平臺組件共享類型定義
- 在運行時響應(yīng)事件,比如按鈕點擊或用戶輸入
- 保存內(nèi)部狀態(tài)時在不同
JSON-driven界面之間轉(zhuǎn)換 - 不要在build-time時實現(xiàn)全部用戶組件的渲染,我們嘗試用Lona格式來處理。
Server-driven rendering框架已經(jīng)提供了大量的價值,允許我們直接遠程試驗或更新功能。
Epoxy Components
2016年我們開源了Android的 Epoxy 。Epoxy 是一個能夠使混合 RecyclerViews、 UICollectionViews 和 UITableViews 變得簡單的框架?,F(xiàn)在,大多數(shù)新的界面使用Epoxy。如此做允許我們打破每個界面形成獨立的組件,實現(xiàn)懶渲染?,F(xiàn)在,我們有了 Android 和 iOS 的 Epoxy。
iOS上如下:
BasicRow.epoxyModel(
content: BasicRow.Content(
titleText: "Settings",
subtitleText: "Optional subtitle"),
style: .standard,
dataID: "settings",
selectionHandler: { [weak self] _, _, _ in
self?.navigate(to: .settings)
})
在 Android 上,我們寫了 DSLs in Kotlin 來實現(xiàn)組件的易寫和類型安全:
basicRow {
id("settings")
title(R.string.settings)
subtitleText(R.string.settings_subtitle)
onClickListener { navigateTo(SETTINGS) }
}
Epoxy Diffing
在React中,從render返回組件列表,React的性能關(guān)鍵是組件能評判代表你想要繪制的實際界面或網(wǎng)頁的數(shù)據(jù)模型。我們?yōu)镋poxy創(chuàng)建了相似的概念。在Epoxy中,你可以在 buildModels 中為所有界面聲明模型。配合優(yōu)雅的Kotlin,DSL從概念上與React非常相似,看起來如下:
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...
}
你的數(shù)據(jù)變化的時候,可以調(diào)用requestModelBuild(),界面會被重繪制。
iOS中代碼如下:
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)
}
})
}
}
A New Android Product Framework (MvRx)
最近開發(fā)中最令人興奮的是一個新的框架,我們內(nèi)部稱為MvRx。MvRx集中了 Epoxy、Jetpack、RxJava 和 Kotlin 的許多準則,從React到比之前更容易更無縫構(gòu)建場景。它是一個更靈活的框架,基于我們從React觀測到的最好的開發(fā)模式。它也是線程安全的,幾乎所有都運行在主線程之外,確?;瑒雍蛣赢嬃鲿?。
目前為止,框架已經(jīng)在很多場景中使用,幾乎消除了處理生命周期的必要。我們目前通過大量的 Android 產(chǎn)品磨練框架,如果繼續(xù)成功的話未來計劃開源。以下是創(chuàng)建一個網(wǎng)絡(luò)請求功能界面的完整代碼:
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簡單地構(gòu)建Fragment,可持續(xù)話,TTI追蹤和大量其他特性。
iOS 相似的框架也在早期測試中。
Iteration Speed
從React Native換回原生之后最明顯的一件事情就是迭代速度。從我們的可靠測試中看,從一兩秒到等待15分鐘是不可接受的。幸運的是,我們能夠提供一些必要的安慰。
我們構(gòu)建 Android 和 iOS 的基本部件,使你只需編譯app的一部分,其中包括一個launcher和所依賴的庫。
在 Android 中,使用gradle product flavors。我們的框架如下:

新的中間件能使工程師創(chuàng)建和開發(fā)更輕巧的 app。配合IntelliJ module unloading明顯改善build和MacBook Pro的IDE性能。
我們用腳本來創(chuàng)建新測試風(fēng)格,過去的幾個月我們已經(jīng)創(chuàng)建了超過20個。用新的flavors開發(fā)平均比之前快2.5倍,5分鐘以上的build的比例下降15倍。
對于此,我們用了動態(tài)生成。
相似的,在iOS中的框架如下:

同樣的系統(tǒng)下,build速度快3到8倍
總結(jié)
很高興成為一個不懼怕新技術(shù)的公司,我們努力保持高質(zhì)量、速度和開發(fā)的經(jīng)驗的增長。最后,React Native是一個優(yōu)秀的工具,給了我們在移動端開發(fā)的更多思考。