[译] 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前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索