Airbnb RN经验总结 - 第五篇
期待更精彩的未来
即使现在正用 RN 试验,我们同时也在原生平台加强努力。
今天,我们线上和正在开发的已经有了很多项目。
有些项目受 RN 亮点和最佳实践经验启发。
服务驱动渲染
即使没有用 RN, 我们仍然看到编写一次生产代码的好处。
我们仍然重度依赖统一设计语言系统,很多界面跨平台看起来几乎一样。
某些团队已经开始尝试统一的强大服务端驱动渲染框架。
服务端向设备发送数据描述渲染组件,屏幕配置和将会发生的动作。
每个移动平台然后解析数据,渲染原生界面,甚至整个流程使用 DLS 组件。
服务端驱动渲染很大时候伴随着一系列挑战。
-
保持向后兼容的同时安全更新组件定义
-
跨平台共享组件类型定义
-
运行时响应事件,如,按钮触摸和用户输入
-
维护内部状态同时在多个 JSON 驱动界面之间切换
-
渲染构建时没有实现的定制组件,我们正在试验 lona 格式
服务端驱动渲染框架已经可以提供很大价值,允许尝试和在线快速更新功能。
Epoxy 组件
2016 年,我们开源 android 的Eproxy
。
Eproxy 框架能够很容易集成 RecyclerViews, UIColletionViews 和
UITableViews。现在很多新界面在用 Eproxy。
采用框架可以允许我们分解每个屏幕成为独立的组件并且实现懒加载。
安卓上我们已经用 kotlin 编写 DSL,实现组件易于编写和保持类型安全。
basicRow {
id("settings")
title(R.string.settings)
subtitleText(R.string.settings_subtitle)
onClickListener { navigateTo(SETTINGS) }
}
iOS 代码示例
BasicRow.epoxyModel(
content: BasicRow.Content(
titleText: "Settings",
subtitleText: "Optional subtitle"),
style: .standard,
dataID: "settings",
selectionHandler: { [weak self] _, _, _ in
self?.navigate(to: .settings)
})
Epoxy 比较
React 中会从 render 函数中返回组件列表。
React 性能的关键是组件只是真实 HTML 视图的数据模型展示。
组件树会进行比较,只有变更部分才会被分发。
我们创建 Epoxy,采用了相似的概念。
Eproxy 中,可以在buildModels
中声明整个屏幕的模型。
Eproxy 和优雅的 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()
方法,然后会重新渲染屏幕,分发优化后的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)
}
})
}
}
全新安卓产品框架(MvRx)
近期最激动人心的开发成果是我们正在开发的内部称作MvRx
的新框架。
MvRx 结合 Eproxy、JetPack、RxJava 和 Kotlin, 采用了很多 React 的准则,比以前更容易创建新屏幕。
它仍然是一项争议中的灵活框架,开发过程中借鉴很多通用开发模式,同时西区 React 中最佳实践。
它是线程安全,几乎所有处理都会脱离主线程,让滚动和动画十分流畅和平滑。
目前,它应用在很多界面上,几乎减少处理生命周期的需要。
我们目前正在一系列安卓产品中试用,如果后面持续成功计划开源。
下面是创建功能界面,发送网络请求所需的完整代码。
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 有简单构造函数,处理片段参数,跨进程重启时维持 saveInstanceState。
TTI 追踪以及其他功能。
迭代速度
切换回原生一大显著变化时迭代速度。
从以前测试新功能只需 1 到 2 秒切换到等待 15 分钟是无法接受的。
幸运的是,我们能够提供更多的需要的时间释放。
我们基于安卓和 iOS 开发工具,保证可以编译应用的一部分,包含启动器和依赖的特定功能模块。
安卓上,会使用gradle product flavors
, gradle 模块看起来会是这样:
这个新层次转变可以让工程师构建和开发应用的一小部分。
和IntelliJ module unloading
一起显著提升 MacBook Pro 上的构建和 IDE 性能。
我们创建了新测试风格的脚本,不出几个月,已经写了超过 20 个。
开发构建速度平均提升 2.5 倍,构建时间超过 5 分钟的百分比同比下降 15 倍。
iOS 平台类似模块分布:
iOS模块相同的系统构建速度会快 3 到 8 倍。
总结
在一家无惧尝试新技术,仍然保持高质量、速度和工程师体验追求的公司工作是很让人激动的。
最后,RN 是一个开发功能的必要工具,给予我们在移动开发上新的思考方向。
如果你想要开始采用 RN 开始开发之旅,请分享给我们。
译者注
-
原文有删减,因译者水平有限,如有错误,欢迎留言指正交流