再见 ReactNative,你好 Ionic。
在过去的两年里,我一直在为 Android 和 iOS 开发 ReactNative (RN) 应用程序,我越来越怀疑 ReactNative 是否仍然是当今移动应用程序的最佳选择。
另一种选择是为 Android 编写原生 Java,为 iOS 编写 Objective-C/Swift,这也可能是好是坏,具体取决于你计划用你的应用做什么。你的应用在 UI/UX 和性能方面越复杂,就越有可能选择由不同的开发团队开发两个应用,甚至可能共享一些角色,但最终会是两个同名但面向不同目标平台的产品。
另一方面,你越注重功能性而非UI/UX和其他非功能性标准,混合或跨平台应用就越有可能成为好主意,因为你只需维护一个应用程序。因此,用数字来衡量这一优势,有可能节省超过50%的开发资源,这是一个相当大的数字。
在继续之前,我想先澄清一下混合技术与跨平台技术之间的区别。文章经常将它们混淆或当作同义词使用,但实际上,混合应用有一些嵌入式浏览器后端,例如 Ionic/Capacitor,你可以在其中编写原生插件并使用原生 API,而这在运行于浏览器沙盒中的渐进式 Web 应用 (PWA) 中是不可能的。
另一方面,跨平台应用使用由统一运行时控制的原生后端。例如,ReactNative 看起来就像我们在浏览器中使用 React 时通常使用的 JSX 输出,但它会被转发到挂载原生 UI 组件的引擎。
一点历史
当 Android 和 iOS 系统之外还有更多系统,且浏览器集成度不够高时,移动开发确实会遇到一些困难。但这种情况随着时间的推移有所改变。实际上,如今你“只需”支持 Android 和 iOS 系统,而且浏览器拥有 PWA 等新 API,功能非常强大,你当然应该考虑使用普通浏览器作为合适的前端。
由于浏览器沙盒的限制,现在和将来做出正确的决定也很困难,但好消息是,2015 年左右,当浏览器开始支持 PWA 并越来越成为应用程序的外观和感觉时,已经有了 PhoneGap(现在称为 Cordova 框架)和 Ionic/capacitor,Facebook 宣布 ReactNative 作为一种新的跨平台移动技术。
如今,我们仍然在浏览器沙盒中使用 PWA,这些 PWA 存在已知的限制,我们无法规避这些限制,PhoneGap/Cordova 的生命周期将于 2020 年结束,因此不再是一种选择,而我们拥有 ReactNative 和 Ionic/capacitor。
还有更多类似由谷歌和微软支持的 Flutter 或 Xamarin 的技术,但它们的社区发展速度似乎不足以与庞大的 JavaScript/TypeScript 社区竞争。JavaScript/TypeScript 社区不仅运行着 Web 前端和后端,还被用于 ReactNative 或任何混合应用技术,例如 Ionic/capacitor。因此,我允许自己质疑 Flutter 或 Xamarin 是否应该被推荐为当今的移动开发技术,当然,未来可能会有所改变,但就我目前而言,这很难令人信服。
这就是为什么我认为在不久的将来,Android/iOS 和基于 JS/TS 的混合或跨平台应用技术才会真正实现原生开发。凭借过去两年使用 ReactNative 的经验,以及在当前应用中使用的 Ionic 概念验证,我终于对下一个移动应用将使用的技术有了初步的判断。让我们先从使用 ReactNative 时遇到的一些问题开始。
ReactNative 的问题
免责声明:部分问题可能不再发生,或者是由我们自身的原因造成的。最终,调查工作花费了大量时间,我不记得在其他前端项目中遇到过如此棘手的问题。
iOS HTTP2 nginx 兼容性问题
HTTP2 的妙处在于,实际上任何技术的网络层都可以在运行时升级到它(如果服务器支持的话),而无需更改应用程序代码,因此它对于应用程序来说是透明的。
在 iOS 14.x 及更高版本上,我们遇到了奇怪的 HTTP2 协议错误。深入 ReactNative 网络调试并阅读原生错误日志后,我们最终发现,问题似乎出在并行发送请求上。我们在网上搜索了各种帖子,有人说应该在 iOS 上修复,而另一些人则说应该在 nginx 上修复或强制使用 HTTP1。只有后者才有可能快速修复,但由于 nginx 本身处于实验阶段,而且 nginx 是系统关键组件,因此我们决定在受影响的版本上串行发送 iOS 请求,希望在官方 nginx 版本修复该问题或我们支持的 iOS 版本修复该问题后,可以取消这种临时解决方法。
我无法判断这是否是 ReactNative 的问题,但我不记得在浏览器中遇到过这样的问题,并且 nginx 版本是官方的 AWS EKS ingress nginx。
RN 更新
移动开发的黄金法则之一是,不要等待太久才进行更新。这就像一颗定时炸弹,因为在我看来,iOS 和 Android API 开发目前仍处于一个发展阶段:它们不太注重 API 的稳定性,而是频繁地对新 API 进行重大更改,同时弃用旧 API 版本。这种做法会对所有依赖项产生影响,进而影响你的应用程序。
这意味着 ReactNative 更新频繁,许多依赖项,尤其是新依赖项,可能不支持之前的 RN 版本。在某些时候,我们已经拥有了实现新功能所需的一切,而无需添加新的依赖项,那么你最好对关注更新有不同的看法。
在我们的案例中,我们遇到了下一个棘手的概念问题,迫使我们添加了一个与当前 RN 版本不兼容的新依赖项。由于我们的客户大约 20 个月内都不需要更新 RN,我们的更新建议没有得到足够高的优先级,因此被推迟到了未来。但后来我们不得不更新 RN,于是又出现了对 RN 的负面批评。
我们其实知道这可不是下午就能搞定的小任务,ReactNative 更新助手会遇到补丁问题,需要手动操作。先不说源代码、资源或依赖元数据(例如 lock 文件或自动生成的 json 资源),我们还有:
- iOS:1600 行配置
- Android:500行配置
而且我们也不采用手动链接,因为手动链接会破坏配置,所以这实际上是签入代码库的原始配置设置。每当有新的 RN 版本发布时,它们都会提供一个新的空项目模板,更新助手会尝试修补您的项目。版本更新造成的差异越大,修补失败的可能性就越大。当我们尝试从 0.63.3 升级到 0.66.3 时就发生了这种情况。
差异毫无意义,所以我们手动比较了文件。在那之前,我们对 iOS 和 Android 的设置经验并不丰富。这花费了大量时间,尤其是 iOS 的设置,因为你必须修改并运行安装程序,直到它以某种方式正常工作。有时,某个依赖项无法构建,你不得不调查原因,直到设置中出现下一个问题,错误消息并没有告诉你任何关于实际问题的信息,而是像“无法读取 /some/xconfig.rb”这样的错误,它看起来很奇怪,因为它是一个绝对路径,但查看自定义脚本后发现,某个环境变量没有为“${SOME_PATH}/some/xconfig.rb”设置,这只是众多示例中的一个。
好处是,它或多或少是迭代式的,但我们需要很多次迭代,大概花了两三周的时间才把所有问题都解决。当然,我们还必须更新 npm 包,因为最终当我们把应用程序部署到设备上时,由于运行时出错,它无法启动。
简而言之,iOS 的设置过程充斥着一堆乱七八糟的 Xcode 项目配置,几乎无人能读懂,而且自定义的内联 Bash 脚本运行着 Ruby 或 Node.js 脚本,设置了隐藏的环境变量,总而言之,调试起来非常困难。Android 的设置则简单得多,但也需要一些时间。
RN“核心”包问题
ReactNative 运行时提供了一些在浏览器中也能找到的 API,以及一个 React 设置,可以将 JSX 转换为原生视图,但它缺少一些移动应用中所需的核心功能,例如原生屏幕、导航手势以及文件系统访问。RN 社区似乎很乐意将这些责任转移到 npm 包上,因为 npm 包为移动应用带来了这些基础功能。参考上一节关于 RN 更新的内容,这些“核心”包与运行时和操作系统 API 紧密耦合,我们当然也必须更新它们。然后,一个包破坏了我们的调试设置。
能够调试应用程序很重要,但我们并非每天都需要。更新之后,我们欣喜地认为问题终于解决了,但我们错了,因为现在调试会冻结应用程序,无法再进行调试。解决方案是将底层 JavaScriptCore (JSC) 引擎更改为 Hermes,并使用 Flipper 作为工具。
虽然感觉 JS 引擎的变更是下一个即将到来的噩梦,但实际上只是一行配置的变更。引入 Flipper 的工作量更大,但与 RN 的更新相比,这还算可以接受。
调试
由于 ReactNative 并非混合应用,虽然感觉上我们有 JSX 和 CSS,但我们无法使用浏览器端的开发者工具等常见的调试工具。这种说法并不完全正确,因为 React-Native-Debugger 实际上是 Electron 应用,它模拟了本地机器上的运行时,并且提供了开发者工具,可以像在浏览器中一样调试网络请求,但并非所有功能都能正常工作(例如文件上传)。
UI 调试也一样,你可以在浏览器开发工具中更新 CSS 类或样式,但 ReactNative 的调试功能比较小众。要么直接用它们,要么在 IDE 中默认使用热模块刷新,然后检查设备,但有时这些功能对样式也不起作用。如果你了解浏览器的调试功能,你可能会觉得 ReactNative 有点难以理解,甚至有点迷失。不过,ReactNative 似乎正在逐渐完善,比如 Flipper 的集成度越来越高,更利于调试。
如果不是 ReactNative,还有什么?
正如我之前提到的,这很主观,但对我来说,剩下的移动应用技术仍然是原生开发或 Ionic / 电容器。选择哪一种应该不太难,因为你可以用 Ionic / 电容器为 iOS、Android 和浏览器应用使用相同的技术;但你可能出于 UI 性能方面的考虑,或者你已经拥有了性能优秀的原生开发团队,所以仍然希望回归原生移动开发的本源。
离子/电容器
现在我得解释一下为什么我总是写“Ionic / Capture”。Capture 是一种将浏览器运行时带到设备上的技术,而 Ionic 是一组看起来像原生的 UI 组件。所以,如果你的 Web 应用拥有响应式 UI,你也可以将 Capture 集成到你的移动应用中,使其成为一个移动应用。
与 ReactNative 的一些缺点进行比较
- 在任何浏览器中使用所有浏览器工具在本地计算机上进行调试始终是可行的,您也可以使用 Safari 调试 iOS 应用程序,或使用 Chrome 调试 Android 应用程序。无论如何,根据我们的 ReactNative 经验,大多数调试会话与设备无关,在浏览器运行时,这更有可能不是设备问题。
- “核心”软件包由 Ionic 社区维护,并由公司本身支持
- 移动设备上使用原生 web 视图的全功能浏览器运行时和桌面浏览器本身允许使用浏览器 API,例如 fetch-API、web-worker 或 IndexedDB 应该可以在任何地方使用,而 WebSQL 仅支持基于 chrome 的前端,例如桌面版 Chrome 或 Brave 或 Android 上基于 chrome 的 web 视图,但使用某些基于 Safari 的 web 视图的 Firefox、Safari 或 iOS 根本不支持。
- 就像 ReactNative 一样,我们不必每次都进行完整构建,因为热模块重新加载可以更新 JS 源,但另一方面,完整的电容器构建需要几秒钟,而我们当前的 ReactNative 应用程序需要大约 2-3 分钟才能完成完整构建,有时当您必须更新所有内容时,更快的构建更好
这种比较可能有点不公平,因为它是一个功能齐全的 RN 应用程序,而 Ionic 应用程序几乎是空的,当然构建时间也更好,但我认为它不会出现我们在使用 ReactNative 时遇到的问题:
- 该应用程序首先使用的是基础的浏览器 API。这主要是由 HTML、CSS 和 JS 组成的浏览器 DOM,用于 UI,显然永远不会中断。然后,由 Web 视图提供但未由 Capturer 实现的 fetch-API 或 IndexedDB 也具有非常稳定的 API。
- 手势、动画和导航可能并非 100% 还原原生用户体验,但 Ionic 再次使用了浏览器 API,我们可以使用像 react-router 这样的知名库进行导航。相比屏幕名称和参数,用 URL 来思考要容易得多。
- 最后,也不可能中断调试:)。
结论
作为开发者,我们务必确保代码与实际代码的距离不要太远。这意味着我们应该能够频繁地修改和验证代码,而且几乎无需任何成本。像浏览器这样的成熟技术已经为应用程序提供了如此多的自省功能,我们可能只会用到其中的几个,但至少我们是站在巨人的肩膀上的。
我也不觉得 Web 应用的外观和感觉像移动应用有什么问题,而且我认为“一个 UI 搞定一切”总体上是个好主意,还能省钱。如果你想修改桌面版的某些 UI 组件,只需左右移动即可,但不要修改整个应用,因为我觉得这样做不值得花时间。
Ionic 是一个很好的例子,说明了如何实现这一点,并且从我在概念验证中阅读和测试的内容来看,我已经建议从 ReactNative 切换到 Ionic。
鏂囩珷鏉ユ簮锛�https://dev.to/daaitch/good-bye-reactnative-hello-ionic-2016