[译] Airbnb 在 React Native 上下的赌注(五 — 完结篇):Airbnb 移动端路在何方?...

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值