如何在Shopify管理本地代码和React Native

256 阅读9分钟

Shopify的本地代码和React Native的管理

一年前,我们宣布决定将我们的赌注和开发者资源放在React Native上。正如你所预料的那样,这一举动在内部引起了兴奋和忧虑,但一年之后,我们仍然相信我们做出了正确的决定。

但是,做出正确的决定并不是免费的。我们不得不重新思考我们在移动领域的最佳开发实践,重新规范模式,并选择和拒绝采用哪些开源项目(或者,就Software Mansion的ReanimatedGesture Handler框架而言,选择在财务上支持哪些项目)。更重要的是,我们必须确定如何利用React Native,同时仍然使用iOS和Android的本地功能。这篇文章特别关注一个话题:我们何时以及如何编写原生代码。我将提到我们的 "销售点 "应用--它影响了我们在Shopify的React Native代码编写方式。

我所说的 "原生代码 "是什么意思

如果你是一个Android或iOS开发者,你总是在写原生代码。但在React Native领域,这是你只有在叹息和重读Native Modules文档后才会去做的事情。在Shopify,我们在开始接触React Native时,对编写本地代码的态度是 "只有在绝对必要时"。这意味着,如果你正在写一个视图,我们强烈反对开发人员制作一个本地UI组件本地模块

在大多数情况下,这种态度对我们的开发者来说是一个很好的北极星--特别是当我们开始学习React Native运行时的黑暗和灰尘角落的过程。如果你只做 "最好 "的决定,你永远不会真正意识到为什么有些决定比其他决定更好。但是...

总是有一个 "但是"...

我们最终遇到了这种方法的限制。首先,在编写用户界面的时候,很容易就把滚动、拖放和高性能结合在一起的组件搅乱。我们维护的一个特别困难的组件是一个可以长按的按钮网格(就像iOS的主屏幕),使得这些项目可以拖动和重新排序。滚动视图中可能有数百个按钮,根据瓦片类型有不同的设计。这个视图有很多复杂的动画和手势交互。我们想知道我们是否对React Native的要求太高了--我们是否应该使用Native UI Components来编写这些视图?

另一个令人沮丧的问题是关于后台进程。为了支持应用程序的离线模式,我们需要定期将本地数据库与服务器同步。我们发现,随着后台作业数量的增加,由于单线程的JavaScript(JS)和单线程的UI线程的结合,我们的响应能力很差。工作中的大部分重活(网络和数据库访问)都是由本地库处理的,我们认为这些库是 "表现良好 "的(不会阻塞JS或UI线程)。但我们发现,每当作业运行时,应用程序几乎完全没有反应。

我们的作业处理程序的JS实现是一个服务列表,它排队等待小的作业功能,整个作业系统在计时器上每分钟运行一次。工作之间的小延迟释放了主线程的交互,我们是这么认为的。但在React Native中工作的现实,特别是在网络和原生模块交互严重的情况下,过桥是,目前是一个昂贵的操作。

我们决定,在React Native上 "全力以赴 "意味着我们需要在适用的情况下达到本地代码的解决方案。

当我们编写本地代码时

你可能会惊讶地发现,我们坚持了我们最初的UI方法!事实上,我们更倾向于使用本地代码。事实上,我们甚至更倾向于它。我们决定将我们的UI层放在React Native中--即使是复杂的视图--也比为Android和iOS维护单独的UI组件要好。使用why-did-you-render、React devtools profiler和我们自己的Application Performance Index测量工具,我们提高了渲染性能,并利用Reanimated 2来处理我们的拖放代码。对性能和用户体验的最大改进是由于减少了重新渲染和懒惰渲染的组合。如果你正在进行类似的重构,请考虑你可以在运行时备忘什么,以及你可以提前执行和存储什么计算。我们的FPS提高了很多,现在这个问题在所有平台上都得到了解决。

另一方面,后台作业将受益于只有本机代码才能使用的线程框架。我们决定开始一个实验,探索一个本地编写的作业管理器。

我们如何编写它

我们有两个选择:用Kotlin为Android编写一个新的作业管理器并将其移植到Swift/iOS ,或者完全尝试一个新的平台,即Kotlin多平台模块

使用Kotlin多平台模块(通常缩写为KMM),你有一个代码库,可以在Android和iOS上运行(也可能是网络!)。需要注意的是,任何平台特定的代码,如文件I/O、网络I/O、访问硬件或操作系统功能,如蓝牙和共享偏好,都被放入expectactual 类中,或作为符合协议的依赖注入。

我们的用例为这个平台完美地塑造了。后台工作形成了一个定义明确的图形,从获取到持久化(和删除陈旧的数据),可以使用Kotlin标准库来编写。我们项目中需要操作系统特定代码的部分可以使用Kotlin多平台支持的开源库来处理,比如SQL数据库的SQLDelight 和GraphQL请求的Apollo,或者我们只是围绕文件I/O、SharedPreferences/NSUserDefaults和后台线程处理(结合Kotlin的coroutine库)编写自己的包装器。

大部分的代码作为共享的、与操作系统无关的代码存在于common/ 。在写这篇文章的时候,common/ 中有3,447个LOC,iOS/android/ 文件夹中分别有170和178个LOC--大约95%的代码共享是非常棒的!同时,该代码在性能上也比原来的JS代码有了很大的改进。以前,一个中等规模的商店的初始同步操作至少需要30秒,但新的同步管理器只需2-3秒就能完成同样的任务。有现有的代码可以学习,改善了代码组织和整体架构。

将Kotlin多平台添加到你的项目中

有两种方法可以将KMM添加到你的项目中:

  1. 直接将KMM构建步骤添加到作为现有React Native项目一部分的Android项目中。
  2. 创建一个新的KMM项目,并将其作为一个库导入你的应用程序。

我推荐第二种方法,作为一个独立的库,你不得不在模块和应用程序之间有明确的界限。作为奖励,你可以创建小型的Android和iOS应用程序,在与应用程序的其他部分隔离的情况下测试你的库。下面是我们如何设置的。一般来说,我建议遵循文件中的设置说明,它使用IntelliJ来生成KMM库,但这里有一些更 "精确 "的说明。它们对我们很有效,但就像所有构建工具的设置一样,你的里程可能会有所不同。这涵盖了Android项目的设置和iOS框架的生成,并假设了React Native应用程序的起点。对于更深入的说明,我建议阅读Better Programming上的这些文章

1.组织项目文件夹

我们把我们的 "native-sync "项目组织成根项目文件夹外的multiplatform/的一个子文件夹。

2.将库文件夹添加到项目中

android/settings.gradle ,将库文件夹添加到你的项目中,并在build.gradle ,将其作为一个依赖项添加。

3.创建新的KMM库

multiplatform/native-sync/项目中,我们需要做一点工作,使沙盒应用程序和导出的库。gradle文件中有太多的细微差别,无法显示所有的构建选项。希望这个设置能让你的库得到编译,但如果这些说明对你不起作用,使用IntelliJ项目向导是创建一个新的KMM库的推荐方法。

在我们的案例中,我们决定通过手动创建和编辑gradle文件来创建库,以使我们对构建设置有最大的控制和了解。

4.构建框架

要构建iOS框架,你可以使用以下命令:

/gradlew :common:packForXcode -Pkn.iOS.target="arm".

这个命令的输出是一个框架(在common/build/ 文件夹中),你可以把它包含在你的Xcode项目中。这个命令应该作为构建你的Xcode项目的一部分来运行.打开你的项目的Build Phases标签,并添加一个New Run Script Phase。

从这里,我们在React Native Android和iOS应用程序中创建了本地模块,这些模块作为React Native应用程序和KMM库/框架之间的中间环节。

缺点

在大多数情况下,编写Kotlin多平台模块的感觉就像为Android编写Kotlin代码,但只能访问标准库。对于来自Swift的开发者来说,他们有一个陡峭的学习曲线:首先,他们需要学习React Native和TypeScript以及这个生态系统,然后他们需要学习Kotlin和Kotlin多平台支持的特定子集。安卓开发者有一个优势,因为他们可以利用他们现有的Kotlin知识。当然,如果他们的背景是Java,那就没那么容易了。

在完成了大部分的背景同步项目后,我们对Kotlin多平台的可行性充满信心,因为在大多数情况下,我们需要使用本地代码的解决方案。我们的移动应用开发者要么来自Kotlin,要么来自Swift,虽然Swift开发者与Kotlin同行相比处于劣势,但大多数人都认为,Kotlin和Swift在语言设计和功能方面有非常相似的 "感觉"。

这篇文章涉及到许多开源项目,任何时候你都应该问自己:在可预见的未来,这将被维护,或者我们可以自己维护它?Facebook的React和React Native项目非常受欢迎,而JetBrains在Kotlin方面也取得了成功。我们认为,这两家公司都不可能从这些项目中走出来。即使是Kotlin多平台,一个相对较新的工具,也被许多公司和项目采用。

学习React Native经常涉及到快速开发和跨平台功能。希望这篇文章能帮助你决定何时以及如何接触原生代码解决方案。