- 原文地址:What’s Next for Mobile at Airbnb:: Bringing the best back to native
- 原文作者:Gabriel Peal
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:ALVINYEH
- 校對者:DateBro
Airbnb 移動端路在何方?
發揮原生最大的潛力
這是系列部落格文章中的第五篇,本文將會概述使用 React Native 的經驗,以及 Airbnb 移動端接下來要做的事情。
激動人心的時刻即將來臨
即使當初在嘗試使用 React Native 時,我們也同時加快了原生的開發。今天,我們在生產環境或正在進行中的專案方面,有許多令人激動的計劃。其中一些專案的靈感,來自我們使用 React Native 的最佳部分和經驗。
伺服器驅動渲染
即使我們已不再使用 React Native,但也看到了只編寫一次產品程式碼的價值。我們仍然非常依賴通用設計語言系統(DLS),因為許多頁面在 Android 和 iOS 上幾乎一模一樣。
幾個團隊已經嘗試開始在強大的伺服器驅動的渲染框架上達成一致。使用這些框架,伺服器將資料傳送到裝置,描述需要渲染的元件,頁面配置以及可能發生的操作。然後,每個移動平臺都會對這些資料進行解析,並使用 DLS 元件渲染原生頁面,甚至是整個流程。
伺服器驅動的大規模渲染還有很多難題。下面是我們正在解決的幾個問題:
- 在保持向下相容性的同時,需要安全地更新元件定義。
- 跨平臺共享元件的型別定義。
- 在執行時響應事件,如按鈕點選或使用者輸入。
- 在保留內部狀態的同時,在多個 JSON 驅動的螢幕之間進行過渡。
- 在構建時渲染完全沒有現有實現的自定義元件。我們正在試驗 Lona 格式。
伺服器驅動的渲染框架已經提供了巨大的價值,我們可以即時實驗和更新功能。
Epoxy 元件
2016 年,我們開源了 Android 的 Epoxy。Epoxy 是一個框架,可以實現簡單的異構 RecyclerView、UICollectionView 和 UITableView。今天,大多數新頁面都採用了 Epoxy。這可以讓我們將每個頁面拆分為獨立的元件,實現延遲渲染。現今,我們在 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 上,我們利用使用 Kotlin 編寫 DSL,使編寫元件更加簡單和型別安全:
basicRow {
id("settings")
title(R.string.settings)
subtitleText(R.string.settings_subtitle)
onClickListener { navigateTo(SETTINGS) }
}
複製程式碼
Epoxy Diffing
在 React 中,利用 render 可返回一個元件列表。React 效能的關鍵在於,這些元件只表示你要渲染的實際檢視/HTML 的資料模型。然後對元件樹進行擴充套件,只渲染更改的部分。我們為 Epoxy 建立了一個類似的概念。在 Epoxy 中,你可以在 buildModel 中為整個頁面宣告模型。與優雅的 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()
}
}
// 其餘模組程式碼放在這裡...
}
複製程式碼
每當資料發生變化時,你都要呼叫 requestModelBuild()
,這個方法會重新渲染你的頁面,並呼叫最佳的 RecyclerView。
在 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)
}
})
}
}
複製程式碼
一個新的 Android 產品架構(MvRx)
最近令人非常激動的進展之一是,我們正在開發新架構,內部稱之為 MvRx。 MvRx 結合了 Epoxy、Jetpack、RxJava 的優點,以及 Kotlin 與 React 的許多原理,構建出的新頁面比以往任何時候都更容易、更流暢。它是一個固執己見而又靈活的框架,通過採用我們觀察到的共同開發模式以及 React 的最佳部分而開發出來的。同時它也是執行緒安全的,幾乎所有事情都從主執行緒執行,這使得滾動和動畫都能變得非常流暢。
到目前為止,它已經在各種頁面上正常工作了,並且幾乎不用去處理生命週期。我們目前正在針對一系列 Android 產品進行試用,如果它能繼續取得成功,我們會計劃開源。這是建立發出網路請求的功能頁面所需的完整程式碼:
data class SimpleDemoState(val listing: Async<Listing> = Uninitialized)
class SimpleDemoViewModel(override val initialState: SimpleDemoState) : MvRxViewModel<SimpleDemoState>() {
init {
fetchListing()
}
private fun fetchListing() {
// 這會自動觸發請求並將其響應對映到 Async <Listing>
// 這是一個密封類,可以是:Unitialized、Loading、Success 和 Fail。
// 無需單獨處理成功和失敗的回撥!
// 此請求也是有生命週期的。它將在配置更改後繼續存在
// 在 onStop 之後不會再傳遞。
ListingRequest.forListingId(12345L).execute { copy(listing = it) }
}
}
class SimpleDemoFragment : MvRxFragment() {
// 這將自動同步 ViewModel 狀態並重建 Epoxy 模型
// 任何時候都會發生變化。類似於 React 的渲染方法:如何為每次更改而執行
// 引數或狀態。
private val viewModel by fragmentViewModel(SimpleDemoViewModel::class)
override fun EpoxyController.buildModels() {
val (state) = withState(viewModel)
if (state.listing is Loading) {
loader()
return
}
// 這些 Epoxy 模型不是檢視本身,所以呼叫 buildModels 花銷很小。
// RecyclerView diffing 將自動完成,只有模型的改變才會重新渲染。
documentMarquee {
title(state.listing().name)
}
// 其餘模組程式碼放在這裡...
}
override fun EpoxyController.buildFooter() = fixedActionFooter {
val (state) = withState(viewModel)
buttonLoading(state is Loading)
buttonText(state.listing().price)
buttonOnClickListener { _ -> }
}
}
複製程式碼
MvRx 的架構比較簡單,主要用於處理 Fragment 引數,跨程式重啟的 savedInstanceState 永續性,TTI 跟蹤以及其他一些功能。
我們還在開發一個類似的 iOS 框架,該框架正在進行早期測試。
預計很快會聽到更多這方面的訊息,我們對迄今取得的進展感到興奮。
迭代速度
當從 React Native 切換回原生時,馬上顯現出來的問題就是迭代速度。從一個在一或兩秒就能可靠地測試更改部分的平臺,到一個可能需要等待 15 分鐘的平臺,根本無法接受。幸好,我們也找到了一些補救措施。
我們在 Android 和 iOS 上構建了基礎架構,可以只編譯包含啟動器的應用中的一部分,並且可以依賴於特定的功能模組。
在 Android 上,這裡使用了 gradle product flavors。我們的 gradle 模組看起來像這樣:
這種新的間接層,使得工程師們能夠在應用的一小部分上進行構建和開發。與 IntelliJ 的解除安裝模組配合使用,大大提高了 MacBook Pro 上的構建時間和 IDE 效能。
我們編寫了指令碼來建立新的測試 flavor,在短短几個月內,我們已經建立了 20 多個。使用這些新的 flavor 開發版本平均要快 2.5 倍,花費 5 分鐘以上的構建時間百分比下降了 15 倍。
作為參考,這是 gradle 程式碼段,可用於動態生成具有根依賴性模組的 product flavor。
同樣,在 iOS 上,我們的模組如下所示:
相同系統的構建速度可提高 3-8 倍
結論
很高興能夠成為一家不怕嘗試新技術,同時又努力保持高質量、高速度和良好開發體驗的公司。最後,React Native 是一個發行新功能的重要工具,它為我們提供了新的移動開發思路。如果你想參與其中,請告訴我們!
這是系列部落格文章的第五部分,重點講述了我們使用 React Native 的經驗,以及 Airbnb 移動端接下來要做的事情。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。