[譯] Airbnb 在 React Native 上下的賭注(五 — 完結篇):Airbnb 移動端路在何方?

Airbnb 移動端路在何方?

發揮原生最大的潛力

這是系列博客文章中的第五篇,本文將會概述使用 React Native 的經驗,以及 Airbnb 移動端接下來要作的事情。html

激動人心的時刻即未來臨

即便當初在嘗試使用 React Native 時,咱們也同時加快了原生的開發。今天,咱們在生產環境或正在進行中的項目方面,有許多使人激動的計劃。其中一些項目的靈感,來自咱們使用 React Native 的最佳部分和經驗。前端

服務器驅動渲染

即便咱們已再也不使用 React Native,但也看到了只編寫一次產品代碼的價值。咱們仍然很是依賴通用設計語言系統(DLS),由於許多頁面在 Android 和 iOS 上幾乎如出一轍。react

幾個團隊已經嘗試開始在強大的服務器驅動的渲染框架上達成一致。使用這些框架,服務器將數據發送到設備,描述須要渲染的組件,頁面配置以及可能發生的操做。而後,每一個移動平臺都會對這些數據進行解析,並使用 DLS 組件渲染原生頁面,甚至是整個流程。android

服務器驅動的大規模渲染還有不少難題。下面是咱們正在解決的幾個問題:ios

  • 在保持向下兼容性的同時,須要安全地更新組件定義。
  • 跨平臺共享組件的類型定義。
  • 在運行時響應事件,如按鈕點擊或用戶輸入。
  • 在保留內部狀態的同時,在多個 JSON 驅動的屏幕之間進行過渡。
  • 在構建時渲染徹底沒有現有實現的自定義組件。咱們正在試驗 Lona 格式。

服務器驅動的渲染框架已經提供了巨大的價值,咱們能夠即時實驗和更新功能。git

Epoxy 組件

2016 年,咱們開源了 Android 的 Epoxy。Epoxy 是一個框架,能夠實現簡單的異構 RecyclerView、UICollectionView 和 UITableView。今天,大多數新頁面都採用了 Epoxy。這可讓咱們將每一個頁面拆分爲獨立的組件,實現延遲渲染。現今,咱們在 Android 和 iOS 上都有用 Epoxy。github

在 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 很是類似,看起來像這樣:bash

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、JetpackRxJava 的優勢,以及 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 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索