决斗:React Native 与 Cordova
已发表: 2022-03-11由于智能手机和移动应用程序变得如此流行,Web 开发人员一直在寻找使用 JavaScript 创建移动应用程序的方法。 这种流行导致了许多 JavaScript 框架的开发,这些框架能够在移动设备上运行类似本机的应用程序。 目前,Cordova 和 React Native 是最受欢迎的选择。 Cordova 支持 iOS、Android 和 Windows Phone 移动平台。 另一方面,使用 React Native,Android、iOS 和 UWP 是开发人员的目标。 (UWP 代表通用 Windows 平台,这是微软的平台,允许相同的应用程序在 Windows Phone 10 Mobile、Xbox One 和 Windows 10 上运行。)
从表面上看,React Native 和 Cordova 似乎占据了相同的空间。 然而,与所有技术一样,在某些方面,有一个亮点,而另一个则不足。 因此,为了更好地了解每种技术,并了解它们的优缺点,我们将深入研究每种技术的细节,并在不同学科之间进行比较。
哲学差异
重要的是要记住,React Native 的标语“一次学习,随处编写”不同于通常的跨平台口号“编写一次,随处运行”。 这导致了两件事:首先,我们不能只从我们的 Web 项目中获取现有的 React 代码库,然后只需点击几下就可以将其变成移动应用程序。 然而,React 和 React Native 确实共享了许多关键概念,其中一个例子就是它们的组件系统,因此,React Native 立即让人感到熟悉。 尽管 React 与 React Native 有很多相似之处,但也存在一些核心差异,从处理样式表的方式到我们可以使用的组件类型。
其次,在针对不同平台时,我们可能会发现自己无法共享 React Native 代码。 当我们希望用户界面元素在其特定平台上以本机方式运行时,就会发生这种情况,从而为用户提供更好的体验和对应用程序更原生的感觉。 一个明显的例子是 Android 应用程序中的抽屉侧边菜单,这在 iOS 应用程序中非常少见。
科尔多瓦不认同这种理念。 开始开发纯 Web 应用程序,然后将其捆绑为 Cordova 应用程序,并为我们想要定位的所有(移动)平台重用尽可能多的代码并不少见。
发展自由
在移动设备上,Cordova 在集成的移动 Web 浏览器中运行一个单页应用程序,称为 WebView,然后将其包装为原生应用程序。 虽然它从外部看起来像是一个原生应用程序,但我们的 Web 代码是在移动浏览器引擎中运行的。 对我们来说,这意味着我们不依赖于特定的库或框架。 如果我们使用原生 JavaScript、jQuery、Angular 或其他任何东西,这些选项中的任何一个都可以与 Cordova 捆绑到移动应用程序中。 Cordova 不会强加于我们的技术堆栈。 只要我们有一个index.html
文件,我们就可以开始了。 一个简单的例子是下面的代码片段:
<html> <head> <title>My Cordova App</title> </head> <body> <div>Tap me</div> <script> // Select our element var element = document.getElementById('tapme'); // Send an alert once it was tapped/clicked element.addEventListener('click', function() { alert('Hello there!'); }); </script> </body> </html>
这个例子意味着我们可以使用几乎任何我们想要的东西,比如使用像 NPM 或 Bower 这样的包管理器,使用像 Babel、CoffeeScript 或 TypeScript 这样的转译器,像 Webpack 或 Rollup 这样的捆绑器,或者其他完全不同的东西。 没关系,只要结果是一个index.html
文件,它可以加载我们需要的所有 JavaScript 和样式表。
顾名思义,React Native 建立在 React 之上。 重要的是要理解 React Native 中的 React 部分是它的核心特性之一。 如果你不喜欢 React 的声明性特性,包括 JSX、它的组件化和数据流,那么你可能不会对 React Native 感到满意。 虽然 React Native 对 React 开发人员来说立即感觉很熟悉,但乍一看,还是有一些差异需要记住。 使用 React Native,我们没有任何 HTML 或 CSS。 相反,这项技术专注于 JavaScript 方面。 作为 CSS 的替代方案,样式是内联编写的,而 Flexbox 是默认样式模型。
最简单的 React Native 应用程序看起来类似于这个例子:
// Import the React module for JSX conversion import { React } from 'react'; // Import React Native's components import { View, Text, AppRegistry, TouchableOpacity, } from 'react-native'; // Create an App component const App = () => { // Define our press handler const onPress = () => alert('Hello there!'); // Compose the components we are going to render return ( <View> <TouchableOpacity onPress={onPress} /> <Text>Tap me!</Text> </TouchableOpacity> </View> ); }; // Registers the `App` component as our main entry point AppRegistry.registerComponent('App', () => App);
React Native 有自己的打包器。 它将所有 JavaScript 文件捆绑到一个巨大的文件中,然后由 Apple 的 JavaScript 引擎 JavaScriptCore 使用和执行。 JavaScriptCore 正在 iOS 和 Android 上使用,而 ChakraCore 正在为 React Native UWP 应用程序提供支持。 默认情况下,React Native 使用 JavaScript 转译器 Babel,允许我们使用 ECMAScript 2015+ (ECMAScript 6) 语法。 虽然没有必要使用 ECMAScript 2015+ 语法,但绝对鼓励使用它,因为所有官方示例和第三方模块都包含它。 由于 React Native 负责打包和转译过程,我们的应用程序代码和第三方模块可以利用这些功能,而无需自己配置工具。
总而言之,React Native 是一种以 React 为中心的固执己见的移动开发方法,而 Cordova 允许我们将 Web 技术捆绑在 WebView shell 中。
原生外观
对用户来说重要的一件事是拥有应用程序的原生外观。 由于 Cordova 应用程序通常是简单的 Web 应用程序,因此有一些事情起初可能会让人感到奇怪。 问题的范围可能从缺少点击区域的视觉反馈,到感觉不像在本机应用程序中那样流畅的滚动,再到点击事件有 300 毫秒的延迟。 虽然所有这些问题都有解决方案,但我们应该记住,如果我们希望我们的 Cordova 应用程序感觉尽可能接近原生应用程序,我们可能需要付出一些额外的努力。 在 Cordova 中,我们无法访问任何本机控件。 如果我们想要拥有原生的外观和感觉,我们有两个选择:要么使用 HTML 和 CSS 重新创建原生控件,例如按钮和输入元素,要么实现直接访问这些原生控件的原生模块。 我们可以自己完成,也可以使用第三方库,如 Ionic 或 Onsen UI。 请注意,随着操作系统更新的出现,及时更新它们很重要。 有时,移动操作系统的外观会焕然一新,就像 iOS 7 推出时那样。 拥有一个无法适应的应用程序会使用户失去体验。 我们还可以求助于包含 Cordova 插件,将我们连接到事物的本机方面。 最完整的原生控件之一是 Microsoft 的 Ace 库。
另一方面,使用 React Native,我们可以访问原生控件和开箱即用的交互。 Text
、 TextInput
或Slider
等组件映射到其原生对应项。 虽然某些组件适用于所有平台,但其他组件仅适用于特定平台。 我们越是希望我们的应用程序具有原生的外观和感觉,我们就越需要使用仅适用于该特定平台的组件,因此我们的代码库就越不同。 心灵触摸交互和手势也是 React Native 的一部分。
比较性能
由于 Cordova 只有一个 WebView 可供使用,我们必然会受到 WebView 的限制。 例如,在 4.0 版本之后,Android 终于开始使用(速度更快的)Chrome 引擎作为默认的 WebView。 在使用 iOS 时,在默认 WebView 引擎中运行的应用程序很长时间以来都比 Safari 移动浏览器中的相同应用程序慢得多。 此外,由于 JavaScript 是单线程的,如果我们的应用程序代码中发生的事情太多,我们可能会遇到问题。 这些限制导致动画迟缓,我们的应用程序可能感觉不像我们希望的那样响应。 虽然我们可以在这里和那里使用一些技巧,但最终,我们会受到移动浏览器的限制。

React Native 利用多个线程,因此渲染 UI 元素在它们自己的线程中运行。 因为 React 组件链接到原生视图,所以 JavaScript 并没有在 React Native 中做繁重的工作。
开发人员工作流程
Cordova 提供了一个命令行实用程序来创建新的项目模板、在模拟器中启动应用程序并在生产模式下为实际设备构建应用程序。 大多数时候,我们在桌面浏览器上开发应用程序,以后可能会将其捆绑为移动应用程序。 有了 Cordova 提供的自由,我们需要自己处理开发工作流程。 如果我们想在设备上实时重新加载,我们需要自己实现它。 为了调试 Cordova 应用程序,我们应用了用于调试网站的相同原则。 例如,在 iOS 中,我们将通过 USB 连接我们的移动设备,打开 Safari 及其开发者工具。
React Native 提供了类似的命令行界面,并提供了 Web 开发人员熟悉的开发工作流程。 我们开箱即用地进行实时重新加载。 一旦我们更改了一个 React 组件,我们的应用程序就会重新加载我们所做的更改。 最令人兴奋的功能之一是热模块替换,它部分地重新加载我们所做的组件中的更改,而不会改变应用程序的状态。 我们甚至可以连接到实际设备,看看我们的更改是否像我们期望的那样在真实设备上工作。 我们的 React Native 应用程序可以使用桌面版 Chrome 进行远程调试。 错误处理在 React Native 中很明显; 如果我们遇到错误,我们的应用程序会显示红色背景,并显示堆栈跟踪。 感谢 sourcemaps,我们可以看到错误的确切位置。 当我们点击它时,我们选择的编辑器会在代码的精确位置打开。
可扩展性和对本机功能的访问
在 JavaScript 方面,我们可以自由使用任何 JavaScript 库,包括来自 NPM 的包。 但是,由于 React Native 不是浏览器环境,我们可能会发现很难使用依赖于 DOM 的代码。 React Native 包含 CommonJS 和 ES2015 模块,因此使用这些格式的任何库都易于集成。
Cordova 和 React Native 都能够创建和使用连接到本机端的插件。 Cordova 提供了一个低级 API 来创建我们自己的 API,这给了我们很多控制权,但会导致使用更多的原生和 JavaScript 样板。
如果我们假设用 Objective-C 编写一个 Cordova iOS 插件,它可能看起来像下一个代码片段。 我们的插件只会记录输入参数。
#import <Cordova/CDVPlugin.h> // Create a class that inherits from CDVPlugin @interface Log : CDVPlugin - (void)log:(CDVInvokedUrlCommand*)command; @end // The actual implementation of the class we just defined @implementation Log - (void)log:(CDVInvokedUrlCommand*)command { CDVPluginResult* pluginResult = nil; // We are getting all parameters and taking the first one NSString* echo = [command.arguments objectAtIndex:0]; // We are checking for the validity of the parameters if (echo != nil && [echo length] > 0) { // We are just printing the parameter using the native log method NSLog(echo); // Let's create a result for the plugin pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:echo]; } // Let's send a signal back with the plugin's result [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } @end
为了使用该模块,这段 JavaScript 代码将有助于:
window.log = function(str, callback) { cordova.exec(callback, function(err) { callback('Nothing to echo.'); }, "Log", "log", [str]); };
要使用该插件,我们只需要调用log
函数:
window.log('Hello native!');
另一方面,React Native 遵循不同的理念。 在编写插件时,它会自动将 JavaScript 类型映射到其本机对应类型,这使得将本机代码与 JavaScript 连接起来更加容易。 让我们看一下使用 React Native 创建原生模块所必需的一段代码:
#import "RCTBridgeModule.h" @interface Log : NSObject <RCTBridgeModule> @end @implementation Log RCT_EXPORT_MODULE(); // This makes this method available NativeModules.Log.log RCT_EXPORT_METHOD(log:(NSString *)message) { NSLog(message); } @end
React Native 通过调用RCT_EXPORT_MODULE
和RCT_EXPORT_METHOD
为我们绑定模块。 我们现在可以使用NativeModules.Log.log
访问它,如下所示:
import { React } from 'react'; import { View, Text, AppRegistry, NativeModules TouchableOpacity, } from 'react-native'; // Create an App component const App = () => { // Log with our module once we tap the text const onPress = () => NativeModules.Log.log('Hello there'); return ( <View> <TouchableOpacity onPress={onPress} /> <Text>Tap me!</Text> </TouchableOpacity> </View> ); }; // Registers the `App` component as our main entry point AppRegistry.registerComponent('App', () => App);
虽然我们只仔细研究了使用 Objective-C 在 iOS 中创建模块,但同样的原则也适用于使用 Java 为 Android 创建模块。
我们需要在每个平台的项目文件中链接本机插件。 以 iOS 为例,这意味着我们必须将编译后的原生部分与我们的应用程序链接并添加相应的头文件。 这可能是一个漫长的过程,尤其是在有很多本机模块的情况下。 幸运的是,通过使用名为 rnpm 的命令行实用程序大大简化了这一点,该实用程序已成为 React Native 本身的一部分。
结论:React Native 还是 Cordova?
React Native 和 Cordova 有不同的用途,因此可以满足不同的需求。 因此,很难说一种技术在所有学科中都优于另一种技术。
通过使用 Cordova,您可以快速将现有的单页应用程序转变为适用于不同平台的移动应用程序,代价是交互不一定具有对特定平台的原生感觉。
使用 React Native,应用程序具有更原生的外观和感觉,但代价是为某些目标平台重新实现代码片段。 如果你已经涉足 React 并且对开发移动应用程序感兴趣,那么 React Native 感觉就像是一个自然的扩展。