本文翻译自What’s Next for Mobile at Airbnb
这是我们关于React Native的系列文章的第五篇,描述我们在
React Native
上的经验和我们下一步在Airbnb移动端上的工作。
Exciting Times Ahead
在尝试React Native
时,我们也同时加快在原生上的努力。今天,我们在产品中有了大量令人惊艳的项目。这些项目的灵感来自于我们在学习React Native
时的经验。
服务驱动渲染(Server-Driven Rendering)
虽然我们不再使用React Native
,但我们仍然看到了只写一份产品代码的价值。我们仍然十分依赖我们统一的设计语言系统(design language system , DLS),很多界面在 Android 和 iOS 中看起来是一样的。
一些团队尝试并开始为强大的server-driven rendering
框架达成一致。在这些框架中,server发送数据到设备来描述渲染的组件、界面配置和发生的交互行为。不同移动平台解析数据并渲染原生界面或者完全使用DLS的组件。
大量使用Server-driven rendering
带来了一些挑战,这里是我们的一些解决方案:
- 安全更新组件定义的同时保持向后兼容
- 对于跨平台组件共享类型定义
- 在运行时响应事件,比如按钮点击或用户输入
- 保存内部状态时在不同
JSON-driven
界面之间转换 - 不要在build-time时实现全部用户组件的渲染,我们尝试用Lona格式来处理。
Server-driven rendering
框架已经提供了大量的价值,允许我们直接远程试验或更新功能。
Epoxy Components
2016年我们开源了Android的 Epoxy 。Epoxy 是一个能够使混合 RecyclerViews、 UICollectionViews 和 UITableViews 变得简单的框架。现在,大多数新的界面使用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 上,我们写了 DSLs in Kotlin 来实现组件的易写和类型安全:
basicRow {
id("settings")
title(R.string.settings)
subtitleText(R.string.settings_subtitle)
onClickListener { navigateTo(SETTINGS) }
}
Epoxy Diffing
在React
中,从render返回组件列表,React
的性能关键是组件能评判代表你想要绘制的实际界面或网页的数据模型。我们为Epoxy创建了相似的概念。在Epoxy中,你可以在 buildModels 中为所有界面声明模型。配合优雅的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...
}
你的数据变化的时候,可以调用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)
最近开发中最令人兴奋的是一个新的框架,我们内部称为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() {
// 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
简单地构建Fragment,可持续话,TTI追踪和大量其他特性。
iOS 相似的框架也在早期测试中。
Iteration Speed
从React Native
换回原生之后最明显的一件事情就是迭代速度。从我们的可靠测试中看,从一两秒到等待15分钟是不可接受的。幸运的是,我们能够提供一些必要的安慰。
我们构建 Android 和 iOS 的基本部件,使你只需编译app的一部分,其中包括一个launcher和所依赖的库。
在 Android 中,使用gradle product flavors
。我们的框架如下:
新的中间件能使工程师创建和开发更轻巧的 app。配合IntelliJ module unloading
明显改善build和MacBook Pro的IDE性能。
我们用脚本来创建新测试风格,过去的几个月我们已经创建了超过20个。用新的flavors开发平均比之前快2.5倍,5分钟以上的build的比例下降15倍。
对于此,我们用了动态生成。
相似的,在iOS中的框架如下:
同样的系统下,build速度快3到8倍
总结
很高兴成为一个不惧怕新技术的公司,我们努力保持高质量、速度和开发的经验的增长。最后,React Native是一个优秀的工具,给了我们在移动端开发的更多思考。