去年,我们推出了 PWA ,旨在改善用户在使用缓慢而不稳定的网络时的连接体验。这是我们努力打造产品质量的第一步。 我们收到了社区和客户非常积极的回应,并希望能复制我们的成功。
挑战
我们需要在三种不同的平台上构建:Android,iOS 还有 Web 端(pc和手机)。
这意味着重复的业务逻辑要维护4个代码库,重复造轮子并非是一个最好的选择。
这也意味着引进新的特性或者修改已有的代码必须在4个单独的代码库重复进行。这样的平台根本不具可扩展性并且很快会因平台间失去同步而走想终点。
最终,我们不得不针对3个平台分别建立1个单独的开发小组。
目标
为了攻克这些挑战,我们决定把赌注压在最新兴起的由时髦的前端语言 JavaScript 构建的跨平台原生应用。为此,我们开始围绕下面的主要目标来构建应用:
尽管这些应用是由 JavaScript 编写的,但是它们在用户体验,用户交互响应等方面应该不逊色于原生应用。简单来说,假如你是用户,你使用这些应用的感受应当和那些来自苹果应用商店或者谷歌应用商店的原生应用相同。
这个应用应该能够跨 Android 和 iOS 并最大限度的复用代码。这是符合"DRY"原则的。这样也意味着维护代码将会很便捷,同时添加/修改/删除产品特性涉及的改动会最小化。
最后但同样重要的是,我们的产品工程师团队应该熟悉整个代码的运作并且减少对特定平台原生开发人员的依赖。这也符合软件工程中“增加公共因子"的原则。
堆栈
react-navigation —— 仍处于早期阶段,但它以声明使用 Animated API 的方式解决了颇有争议的导航问题。它也很适合用于处理我们基于 redux 的状态管理系统,因为它是一个纯粹的基于 JS 的解决方案。同时,我们也正在探讨其他本地和混合导航的解决方案。
redux-observable —— JS 生态系统仍然致力于为异步状态管理找出最佳解决方案,但说到底,它更多是“每部分自身”的问题。我们决定使用 redux-observable,因为它可以帮助我们很好的分离副作用,并使用简洁有力的 RxJS 操作处理它们。同时,这种方法还允许我们以相对孤立的方式测试处理我们的副作用代码。
immutable —— 所面临的不愉快的事情是,我们难以在以前的平台上发现由我们的 reducer 突变引起的错误。为了一劳永逸地解决这个问题,我们决定在整个应用程序中使用不可变数据结构。这是通过定制的 reducer factory 实现的,它可以在不可变的和普通的JS数据结构之间进行转换。
ramda —— 尽可能地使用处理我们大部分业务逻辑的纯函数,使其成为一个功能强大的声明代码范例。 Ramda JS 在这方面对我们来说是不可替代的。
redux-persist —— 与网页端应用程序不同,本机应用程序有脱机模式和持久状态的需求。这个库与 redux-persist-migrate 很好地用一个后备 AsyncStorage 层满足了这样的需求。
工具
除了这些常用的工具 — yarn, prettier, eslint和husky,我们还依赖于下列工具:
storybook —它为开发独立的本地组件提供了极好的支持。因此,我们可以将我们的 UI 组件编码为与我们的设计指南的一一对应。我们正在寻求内部部署的可能,以便设计人员也可以访问实际的组件。
codepush — 这是一个 react 本地应用程序真正闪耀的领域。我们在向用户发布更新时使用 codepush 多于 air update,因为它拥有完整的回滚百分比和目标版本。
fastlane — 在 fastlane 中,管理不同的环境(暂存,开发,产品)和自动化构建很简单。我们在内部的 Jenkins CI 中发布一个参数化的构建面板,它管理着从应用程序秘密、代码签名、Test Flight、Crashlytics Beta 上传、内部测试构建的设备注册、通过 codepush 等方式发布 OTA 更新等所有内容。
jest 和 detox — 这个组合为我们的应用程序带来了一个不错的测试平台。鉴于我们必须为原生模块编写 mock,Jest 被证明在配置 react native 开始时确实有点麻烦的,但这么做是值得的。Wix 工程组的属下人员设计的 Detox 为我们简化了端到端的测试流程。
sentry —sentry.io 的工作人员在一定程度上引入了对 react-nativce 应用程序的支持。新的 SDK 添加了大量有用的设备相关数据的错误报告,并提供了本机和 JS 堆栈跟踪的整体报告。
超过 90% 的应用程序的源代码是 JavaScript,并且不会影响其性能和质量。
学习收获
React Native 是一个相对新的平台。其周边的社区仍然在探讨最佳实践以及完成某些事情的正确方法。官方文档是我们发现的作为起步学习的最好资源。以下是我们在深入学习过程中的一些收获:
InteractionManager — 当涉及到 perf,InteractionManager 必不可少。由于 JS 是单线程,社区已经付出了相当大的努力,将开销高昂的任务放到本地线程上运行。有时候你需要在 JS 中执行开销大的任务,并且不能影响你 animation/transition/ 用户交互的 perf。InteractionManager 提供了一套不错的调度 API 来延迟这些高开销的任务,直到所有的 animation/transition/ 交互完成。
requestAnimationFrame — 此概念是从网络上借鉴来的,工作原理类似。一个特定的用例是 Android 设备的涟漪效应。在 apt onPress 处理程序中使用 TouchableNativeFeedback 的常规方法在这里并不一定适用。有时候,你可能看不到“涟漪”。相反,如果你在 requestAnimationFrame 块中封装你的 onPress 处理程序,你会观察到动画是完美可见的。
MessageQueue — React native 是通过桥接实现 JS 和本地程序之间的通信的。因此,在此桥接上会有持续的通信,如果调整不当,可能会影响性能。顾名思义,MessageQueue 上的 spy 方法,让你可以获知此聊天,并查看具体发送的内容。这可能有助于你了解实际发生的情况并提高性能。
setNativeProps — 来自官方文档中显示,“setNativeProps 在 React Native 中的作用等同于直接在 DOM 节点上设置属性”。有时候,处于某些只有你自己知道的原因,你可能需要操纵支持 JS 视图的底层本地视图,以使得 React 渲染进入周期短路。我们只在几个地方使用这种方法,因为其他的一切机制都不能很好完成任务。避免使用该接口或在必要情况下,非常明智地使用它。
Structuring — 从一开始,我们就在代码库中遵循一个简单的组织结构的原则。 我们将 dumb UI 组件与状态视图分开。状态管理都在我们的 epic s和 reducer 中得到处理。我们观察到,随机分散的副作用所生成代码成为保证代码库高性能和可测试性的阻碍。我们使用 redux-observable 的方法帮助我们减少一些类似的痛苦。请看以下示例:
构建 Pipeline
官方文档对 API 和平台本身提供了大量的解读。最后,你需要部署新的崭新的应用程序。这也包括维护用于测试和暂存的多个环境、以非入侵方式混合不同的凭证、生成发行说明并通知所有相关者(产品经理、测试人员和设计人员)等挑战。经过一系列不同策略的尝试和反复,我们迁移到了 Fastlane 上,这样可以使整个过程实现自动化。以下是我们在 iOS 上的 beta 发布循环中的删减版:
这段代码处理了代码签名、注册用于测试的设备、增加内部编号、构建应用程序、上传到 Crashlytics Beta 中、生成发行说明、在 code-push 上推送发布代码和将 sourc-maps 上传到 sentry 、通知 slack channel ,以及最后在 GitHub 上添加发布标签。你可以把本该用于构建的时间做任何其他事情。该代码位于主应用程序代码旁。由于 CI 在每次构建之前都引入了一个新的版本,所以在不破坏 CI 的前提下修改构建 pipeline 是非常容易的。
进阶贴士
阅读此文档和发布说明。
在你安装某些软件,但无法正常工作或无法找到时,yar n需使用- -reset-cache 选项启动。
react-native-debugger — 基于 React Native 的官方调试器的独立应用程序,包含 React Inspector / Redux DevTools。
使捆绑式 Perf Monitor 成为你的良师益友。
总是在真实设备上测试。
前提条件是熟知 React。
脚注
免责声明:我们不推崇在这里提到的任何工具、库、编码实践或软件开发理念。欢迎您对文章进行阅读、学习和采纳,有不当之处,也欢迎您批评和提出反对意见。
React Native使你能够在Javascript和React的基础上获得完全一致的开发体验,构建世界一流的原生APP。
React Native着力于提高多平台开发的开发效率 —— 仅需学习一次,编写任何平台。(Learn once, write anywhere)
Facebook已经在多项产品中使用了React Native,并且将持续地投入建设React Native。