Airbnb RN经验总结 - 第二篇

  • 第一部分: 技术总结
    • 优点
      • 跨平台
      • 统一设计语言系统 DSL
      • React
      • 迭代速度
      • 基础设施投资
      • 性能
      • Redux
      • 原生支撑
      • 静态分析
      • 动画
      • 开源 React/JS
      • Flexbox 布局
      • web 协同
    • 缺点
      • 未成熟的 RN
      • 维护 RN 分支
      • JS 工具链
      • 重构
      • 不稳定的 JavaScriptCore
      • RN 开源库
      • 并行设施和特性工作
      • 崩溃监测
      • 原生 Bridge
      • 初始化时间
      • 初始化渲染时间
      • 应用大小
      • 64 位
      • 手势
      • 长列表
      • RN 升级
      • 可访问性
      • 恶心的崩溃
      • 安卓跨进程维持实例状态
  • 译者注

第一部分: 技术总结

RN 在跨平台框架和 Android、iOS、web 上本身是一个相对新和发展迅速的平台。

在使用 RN 两年后,我们说 RN 的确在很多方面十分先进。

它对于移动端来说是编程方式的转变。

我们已经从 RN 中收获很多,但是它的优点也伴随着明显的痛点。

优点

跨平台

RN 最基本的优点在于可以代码同时可以在安卓和 ios 运行,RN 代码可以实现
95%到 100%共享,只有 0.2%的文件是平台特定的文件,比如,。android.js/.ios.js。

统一设计语言系统 DSL

我们开发跨平台设计语言,DLS,我们设计的组件支持四大平台。

统一设计语言可以确保实现跨平台功能,因为设计、组件、命名和界面可以持续跨平台。

但是必要的时候我们可以编写平台特定代码。

例如,android 平台我们用ToolBar,ios 用UINavigationBar组件,
我们会在安卓上隐藏关闭提示,因为不符合安卓平台设计标准。

我们选择重写组件,而不是包装原生组件,因为保证平台特定 API 独立,可以减少平台开发者的维护工作,但也带来原生和 RN 相同组件的碎片化,功能会无法同步。

React

Reacr 成为最受欢迎的 web 框架有一个原因,简单且强大,可以扩展成大型项目,我们尤其喜欢这些特性:

  • 组件,强制分离 props 和状态,这也保持 React 的稳定性

  • 简化生命周期,android 和 ios 某种程度上,生命周期有点复杂,

函数式组件基本解决这个问题,让学习 RN 变得很简单。

  • 声明式,声明式 React 组件帮助 UI 界面和底层状态保持一致

迭代速度

我们可以用热加载在 1 秒或 2 秒内测试不同平台,即使原生平台的构建性能很快,但从未接近 RN 的迭代速度,最好的时候,原生编译时间是 15 秒,全量构建高达 20 分钟。

基础设施投资

我们开发了额外工具集成原生工具,所有的工具代码,如,网络请求,国际化,试验功能,共享组件渐变,设备信息,账户信息和许多其他功能都封装成单独的 RN API。

这些桥工具只是更复杂工具中一部分,因为我们想基于原生 API 包装 React 可用的方法。

为了保持这些桥工具快速同步更新迭代,新的基础工具开发成为持续过程,基础设施团队让这些变得更简单。

如果没有基础设施支撑,RN 会导致低下的开发速度和用户体验。

如果没有基础设施持续投入,我们相信 RN 不会这么简单结合已有的原生应用。

性能

RN 最大的考虑点之一是性能。
但是,实际上,性能几乎不是问题。

大部分 RN 界面和原生一样流畅,性能问题也只在单一尺寸上考虑。

我们经常看到原生工程师看到 Js,认为效率比 java 慢,但是,快速的业务逻辑和布局逻辑都从主线程分离,在很多时候会提升渲染性能。

我们发现的性能问题,都是重复渲染引起的,可以用shouldComponentUpdat,removeClippedSubviews和 Redux 解决。

但是,初始化和首屏渲染 RN 执行很慢,提高不同界面导航的等待时间。

界面掉帧很难调试,因为Yoga在 RN 组件和原生视图之间进行转译。

Redux

我们用 redux 做状态管理,redux 十分高效,可以保持 UI 和状态的同步,在不同的界面之间共享数据。

但是 Redux 模板代码很多,而且有相对困难的学习曲线。

我们提供通用模板的生成器工具,但仍然是 RN 开发中最麻烦和困惑的部分之一。

原生支撑

因为 RN 的一切都可以通过原生代码桥接,之前我们认为不能实现的很多东西都可以实现:

  • 共享元素渐变,我们创建组件由跨平台原生共享代码支撑,这也在 RN 和原生界面之间有用。

  • Lottie, 我们可以包装原生平台库给 RN 调用

  • 原生网络栈, RN 可以使用原生网络栈,并且实现跨平台缓存

  • 其他功能,我们封装了国际化和试验性功能,这样 RN 也能无缝运行。

静态分析

我们一直在 web 上使用 eslint,但是我们是 airbnb 第一个使用 prettier 的团队,可以在 PR 时帮助减少代码问题。

Prettier 成为 web 基础团队活跃关注的工具。

我们会统计渲染事件和性能,找出哪些界面存在性能问题。

因为 RN 比 web 端还新,还小,成为很多新想法的试验池,现在我们写的很多工具已经被 web 采用。

动画

RN 动画库可以帮助实现流畅的动画,甚至是像滚动视差这样的交互驱动动画。

开源 React/JS

有很多开源 js 项目可供使用,如,redux, reselect, jest 等等。

Flexbox 布局

RN 使用yoga处理界面布局,yoga 是一个通过 flexbox 实现布局计算的 C 库。

web 协同

RN 应用的后期,我们开始构建 web、ios 和 android, 由于 web 也用 redux,

我们发现很多代码都可以在 web 和原生平台直接共享。

缺点

未成熟的 RN

RN 没有原生平台成熟,RN 很新,更新很快。

但是 RN 适用大部分场景,但仍然有不成熟的例子,让在原生协调变得十分困难。

遗憾的是,这些例子很难预测,可能需要很长时间发现。

维护 RN 分支

由于 RN 的不成熟,有时需要对 RN 源码打补丁,除了回馈贡献 RN 官方外,

我们维护 RN 分支,可以快速合并变化,直接提升新版本。

过去两年,我们已经在 RN 分支上提交 50 次代码,这也让 RN 升级异常痛苦。

JS 工具链

JS 是弱类型语言,缺乏类型安全支持很难扩展,同时成为移动工程师学习 RN 的障碍。

我们后来采用Flow,但是杂乱的错误信息带来糟糕的开发体验。

我们也尝试了TS, 但是集成至现有架构中,如,babel 和 metro bundler,

也存在问题。但是我们在 web 上一值使用TS.

重构

JS 弱类型的副作用导致重构变得异常艰难。

重命名 props, 特别是有共有名称的 props, 如,onClick 在多个组件直接传递,让重构成为恶梦。

更糟糕的是,生产环境而不是编译时,重构代码报错,关键是很难添加合适的静态代码分析。

不稳定的 JavaScriptCore

由于 RN 使用 JavaScriptCore 执行环境,我们同样遇到问题:

  • IOS 使用自己的引擎,一般没有问题

  • 安卓没有提供自己的 jscore 引擎,RN 需要打包自己的引擎,但是默认的引擎十分古老,结果我们不得不额外打包一个新版引擎。

  • RN 调试时会附加谷歌开发者工具实例,但是一旦调试工具附加后,所有的 js 会在 V8 引擎中执行,99%的场景下没有问题,但是,在 IOS 上调用 toLocaleSting 成功,只在 android 调试时有用,结果是安卓 JSC 引擎没有引入方法,默认失败,除非调试时使用 V8 引擎执行。

如果不知道这些技术细节,这会工程师带来数天的痛苦调试。

RN 开源库

学习新平台很难且耗时,大部分人只会一个或两个平台。

RN 库有原生桥接,像,地图,视频等等,需要掌握三个平台对应的知识。

我们发现大部分开源库都是由只有一个或两个平台经验的人编写的,这会导致跨平台的未知 BUG。

安卓上,很多 RN 库要求使用相对路径的 node_modules,而不是已发布的 maven 包,这也和社区期望的不一致。

并行设施和特性工作

我们已经在原生平台积累了多年经验,但是,RN 我们完全是空白,不得不基于现有原生设施编写 RN 桥接代码。

这意味着如果工程师需要的功能 RN 官方不提供,要么就某个平台,跳出自己的项目,去编写不熟悉的原生代码,要么等着官方更新。

崩溃监测

我们使用bugsnag做崩溃上报。

我们不得不编写相当数量的工具代码,如,上传源代码映射文件,调用 bugsnag 方法筛选只在 RN 发生的崩溃。

由于 RN 崩溃上报是定制开发,我们有时会发生严重问题,映射文件也没上传,崩溃也没有上报。

最后,如果问题在 RN 和原生代码交互之间发生,由于调用栈不会显示两个平台的日志,调试 RN 崩溃会变得异常艰难。

原生 Bridge

RN 提供桥接 API 来支持原生平台的通信,但很难编写。

首先,它需要三个开发环境都正确设置,我们经历了很多问题,如 JS 的传递类型发生变化,

例如,数字总是被字符串替代,直到经过桥传递时才发现问题。

有时 IOS 不会报错,但安卓会崩溃。

我们直到 2017 年底才开始采用 TS 定义自动生成桥接代码,但已经太晚了。

初始化时间

在 RN 代码首次渲染之前,你必须初始化运行时。

不幸的是,由于的我们应用包大小问题,即使是高配设备也会花费数秒启动。

这让 launch 页面实现几乎不可能。

我们在应用加载时初始化 RN,才减少首屏渲染时间。

初始化渲染时间

不像原生界面,RN 渲染需要至少一个完全单独主线程,执行 JS,yoga 布局线程,主线程循环后,在足够信息完成后首次渲染界面。

我们看到平均初始化渲染时机,ios 280 毫秒,Android 440 毫秒。

结果我们添加额外的 50 毫秒延迟,让所有 RN 界面渐变,只要所有配置加载完成,阻止导航栏闪烁。

应用大小

RN 在应用大小方面也无法控制。
安卓上,RN 大小是每个 ABI 8M, 包括,java 代码,js 代码,原生库,yoga 和 js 运行时。

如果同时引入 64 位和 32 位包进入一个 APK,大小会接近 12M。

64 位

安卓仍然无法使用 64 位 APK 包。

手势

我们避免在 RN 界面中使用复杂手势,因为底层触摸子系统完全不同,社区仍然没有一致的 API 提供。

但是,依然有进步,react-native-gesture-handler 已经到 1.0 版本了。

长列表

RN 已经用 FlatList 实现进步,但是仍然无法实现原生平台的成熟度和扩展性。

许多限制因为线程的关系很难解决。适配器数据无法同步访问,所以可能会看到视图闪烁,因为滚动过快时,数据会异步渲染。

文本在 IOS 系统中无法同步计算,因此无法针对预计算单元高度进行优化。

RN 升级

尽管 RN 升级不部分情况没什么问题,但有些时候很痛苦。

例如,从 RN0.43 升级到 0.49 几乎不可能,因为 0.49 使用 React 16 的测试版。

这里问题大了,因为 web 大部分 React 库都不支持最新版。

2017 年中期为了升级 RN 包装各种依赖成了基础设施的主要工作。

可访问性

2017 年,我们花了很大努力让行动不便人士也能使用平台订房间。

但是 RN 的访问性 API 存在很多漏洞。

为了实现一个最小可运行的访问功能,我们不得不开发自己的 RN 分支,修复各种问题。

只要有一行跨平台修改,就得花费数天时间搞清楚如何添加到 RN 源码中, 合并代码,然后

向 RN 核心代码提交 issue,最后等待 RN 更新。

恶心的崩溃

我们不得不处理一些诡异的崩溃,例如,我们目前碰到@ReactProp注解的崩溃,

在任何设备都无法重现,即使是特定硬件或软件环境也不能重现。

安卓跨进程维持实例状态

安卓会定期清理后台进程,但提供场景同步保持应用状态。

但是 RN 状态只能在 JS 线程中异步访问,即使抛开这个原因,全局 redux 状态也无法兼容,因为 redux 包含可序列化和非序列化数据集合,

包含更多saveInstanceState无法适配的数据,这会导致生产环境的崩溃。

译者注

  • 原文链接

  • 原文有删减,因译者水平有限,如有错误,欢迎留言指正交流

你可能感兴趣的:(Airbnb RN经验总结 - 第二篇)