做为 GSY 开源系列的做者,在去年也整理过 《移动端跨平台开发的深度解析》 的对比文章,时隔一年以后,本篇将从新由 环境搭建、实现原理、编程开发、插件开发、编译运行、性能稳定、发展将来 等七个方面,对当前的 React Native 和 Flutter 进行全面的分析对比,但愿能给你更有价值的参考。css
是的,此次没有了 Weex,超长内容预警,建议收藏后阅。html
临冬之际,移动端跨平台在经历数年沉浮以后,现在还能在舞台聚光灯下雀跃的, 也只剩下 React Native 和 Flutter 了,做为沉淀了数年的 “豪门” 与 19 年当红的 “新贵” ,它们之间的 “针锋相对” 也成了开发者们关心的事情。前端
过去曾有人问我:“他即写 Java 又会 Object-C ,在 Android 和 IOS 平台上能够同时开发,为何还要学跨平台呢?”java
而个人回答是:跨平台的市场优点不在于性能或学习成本,甚至平台适配会更耗费时间,可是它最终能让代码逻辑(特别是业务逻辑),无缝的复用在各个平台上,下降了重复代码的维护成本,保证了各平台间的统一性, 若是这时候还能保证必定的性能,那就更完美了。node
类型 | React Native | Flutter |
---|---|---|
语言 | JavaScript | dart |
环境 | JSCore | Flutter Engine |
发布时间 | 2015 | 2017 |
star | 78k+ | 67k+ |
对比版本 | 0.59.9 | 1.6.3 |
空项目打包大小 | Android 20M(可调整至 7.3M) / IOS 1.6M | Android 5.2M / IOS 10.1M |
GSY项目大小 | Android 28.6M / IOS 9.1M | Android 11.6M / IOS 21.5M |
代码产物 | JS Bundle 文件 | 二进制文件 |
维护者 | ||
风格 | 响应式,Learn once, write anywhere | 响应式,一次编写多平台运行 |
支持 | Android、IOS、(PC) | Android、IOS、(Web/PC) |
使用表明 | 京东、携程、腾讯课堂 | 闲鱼、美团B端 |
不管是 React Native 仍是 Flutter ,都须要 Android 和 IOS 的开发环境,也就是 JDK 、Android SDK、Xcode 等环境配置,而不一样点在于 :react
npm
、node
、react-native-cli
等配置 。flutter sdk
和 Android Studio / VSCode 上的 Dart 与 Flutter 插件。从配置环境上看, Flutter 的环境搭配相对简单,而 React Native 的环境配置相对复杂,并且因为 node_module
的“黑洞”属性和依赖复杂度等缘由,目前在我的接触的例子中,首次配置运行成功率 Flutter 是高于 React Native 的,且 Flutter 失败的缘由则大多归咎于网络。android
同时跨平台开发首选 Mac ,没有为何。git
在 Android 和 IOS 上,默认状况下 Flutter 和 React Native 都须要一个原平生台的 Activity
/ ViewController
支持,且在原生层面属于一个“单页面应用”, 而它们之间最大的不一样点其实在于 UI 构建 :github
React Native 是一套 UI 框架,默认状况下 React Native 会在 Activity
下加载 JS 文件,而后运行在 JavaScriptCore
中解析 Bundle 文件布局,最终堆叠出一系列的原生控件进行渲染。npm
简单来讲就是 经过写 JS 代码配置页面布局,而后 React Native 最终会解析渲染成原生控件,如 <View>
标签对应 ViewGroup/UIView
,<ScrollView>
标签对应 ScrollView/UIScrollView
,<Image>
标签对应 ImageView/UIImageView
等。
因此相较于如 Ionic
等框架而言, React Native 让页面的性能能获得进一步的提高。
若是说 React Native 是为开发者作了平台兼容,那 Flutter 则更像是为开发者屏蔽平台的概念。
Flutter 中只需平台提供一个
Surface
和一个Canvas
,剩下的 Flutter 说:“你能够躺下了,咱们来本身动”。
Flutter 中绝大部分的 Widget
都与平台无关, 开发者基于 Framework
开发 App ,而 Framework
运行在 Engine
之上,由 Engine
进行适配和跨平台支持。这个跨平台的支持过程,其实就是将 Flutter UI 中的 Widget
“数据化” ,而后经过 Engine
上的 Skia
直接绘制到屏幕上 。
因此从以上能够看出:React Native 的 Learn once, write anywhere 的思路,就是只要你会 React ,那么你能够用写 React 的方式,再去开发一个性能不错的App;而 Flutter 则是让你忘掉平台,专一于 Flutter UI 就行了。
额外补充一点,React 的虚拟 DOM 的概念相信你们都知道,这是 React 的性能保证之一,而 Flutter 其实也存在相似的虚拟 DOM 概念。
看过我 Flutter 系列文章可能知道,Flutter 中咱们写的
Widget
, 其实并不是真正的渲染控件,这一点和 React Native 中的标签相似,Widget
更像配置文件, 由它组成的Widget
树并不是真正的渲染树。
Widget
在渲染时会通过 Element
变化, 最后转化为 RenderObject
再进行绘制, 而最终组成的 RenderObject
树才是 “真正的渲染 Dom” , 每次 Widget
树触发的改变,并不必定会致使RenderObject
树的彻底更新。
因此在实现原理上 React Native 和 Flutter 是彻底不一样的思路,虽然都有相似“虚拟 DOM 的概念” ,可是React Native 带有较强的平台关联性,而 Flutter UI 的平台关联性十分薄弱。
React Native 使用的 JavaScript 相信你们都不陌生,已经 24 岁的它在多年的发展过程当中,各端各平台中都出没着它的身影,在 Facebook 的 React 开始风靡以后,15 年移动浪潮下推出的 React Native ,让前端的 JS 开发者拥有了技能的拓展。
Flutter 的首选语言 Dart 语言诞生于 2011 年,而 2018 年才发布了 2.0,本来是为了用来对抗 JavaScript 而发布的开发语言,却在 Web 端一直不温不火,直到 17年 才由于 Flutter 而受关注起来,以后又由于 Flutter For Web 继续尝试后回归 Web 领域。
编程开发所涉及的点较多,后面主要从 开发语言
、界面开发
、状态管理
、原生控件
四个方面进行对比介绍。
至于最多吐槽之一就是为何 Flutter 团队不选择 JS ,有说由于 Dart 团队就在 Flutter 团队隔壁,也有说谷歌不想和 Oracle 相关的东西沾上边。 同时 React Native 更新快 4 年了,版本号依旧没有突破 1.0 。
由于起初都是为了 Web 而生,因此 Dart 和 JS 在必定程度上有很大的通识性。
以下代码所示, 它们都支持经过 var
定义变量,支持 async/await
语法糖,支持 Promise
(Future
) 等链式异步处理,甚至 *
/yield
的语法糖都相似(虽然这个对比不大准确),但能够看出它们确实存在“近亲关系” 。
/// JS
var a = 1
async function doSomeThing() {
var result = await xxxx()
doAsync().then((res) => {
console.log("ffff")
})
}
function* _loadUserInfo () {
console.log("**********************");
yield put(UpdateUserAction(res.data));
}
/// Dart
var a = 1;
void doSomeThing() async {
var result = await xxxx();
doAsync().then((res) {
print('ffff');
});
}
_loadUserInfo() async* {
print("**********************");
yield UpdateUserAction(res.data);
}
复制代码
可是它们之间的差别性也不少,而最大的区别就是: JS 是动态语言,而 Dart 是伪动态语言的强类型语言。
以下代码中,在 Dart
中能够直接声明 name
为 String
类型,同时 otherName
虽然是经过 var
语法糖声明,但在赋值时其实会经过自推导出类型 ,而 dynamic
声明的才是真的动态变量,在运行时才检测类型。
// Dart
String name = 'dart';
var otherName = 'Dart';
dynamic dynamicName = 'dynamic Dart';
复制代码
以下图代码最能体现这个差别,在下图例子中:
var i
在全局中未声明类型时,会被指定为 dymanic
,从而致使在 init()
方法中编译时不会判断类型,这和 JS 内的现象会一致。
若是将 var i = "";
定义在 init()
方法内,这时候 i
已是强类型 String
了 ,因此编译器会在 i++
报错,可是这个写法在 JS 动态语言里,默认编译时是不会报错的。
动态语言和非动态语言都有各类的优缺点,好比 JS 开发便捷度明显会高于 Dart ,而 Dart 在类型安全和重构代码等方面又会比 JS 更稳健。
React Native 在界面开发上延续了 React 的开发风格,支持 scss/sass 、样式代码分离、在 0.59 版本开始支持 React Hook 函数式编程 等等,而不一样 React 之处就是更换标签名,而且样式和属性支持由于平台兼容作了删减。
以下图所示,是一个普通 React Native 组件常见实现方式,继承 Component
类,经过 props
传递参数,而后在 render
方法中返回须要的布局,布局中每一个控件经过 style
设置样式 等等,这对于前端开发者基本上没有太大的学习成本。
以下所示,若是再配合 React Hooks 的加持,函数式的开发无疑让整个代码结构更为简洁。
Flutter 最大的特色在于: Flutter 是一套平台无关的 UI 框架,在 Flutter 宇宙中万物皆 Widget
。
以下图所示,Flutter 开发中通常是经过继承 无状态 StatelessWidget
控件或者 有状态 StatefulWidget
控件 来实现页面,而后在对应的 Widget build(BuildContext context)
方法内实现布局,利用不一样 Widget
的 child
/ children
去作嵌套,经过控件的构造方法传递参数,最后对布局里的每一个控件设置样式等。
而对于 Flutter 控件开发,目前最多的吐槽就是 控件嵌套和样式代码不分离 ,样式代码分离这个问题我就暂不评价,这个真要实际开发才能更有体会,而关于嵌套这里能够作一些 “洗白” :
Flutter 中把一切皆为 Widget
贯彻得很完全,因此 Widget
的颗粒度控制得很细 ,如 Padding
、Center
都会是一个单独的 Widget
,甚至状态共享都是经过 InheritedWidget
共享 Widget
去实现的,而这也是被吐槽的代码嵌套样式难看的缘由。
事实上正是由于颗粒度细,因此你才能够经过不一样的 Widget
, 自由组合出多种业务模版, 好比 Flutter 中经常使用的 Container
,它就是官方帮你组合好的模板之一, Container
内部实际上是由 Align
、 ConstrainedBox
、DecoratedBox
、Padding
、Transform
等控件组合而成 ,因此嵌套深度等问题彻底是能够人为控制,甚至能够在帧率和绘制上作到更细致的控制。
固然,官方也在不断地改进优化编写和可视化的体验,以下图所示,从目前官方放出的消息上看,将来这个问题也会被进一步改善。
最后总结一下,抛开上面的开发风格,React Native 在 UI 开发上最大的特色就是平台相关,而 Flutter 则是平台无关,好比下拉刷新,在 React Native 中, <RefreshControl>
会自带平台的不一样下拉刷新效果,而在 Flutter 中,若是须要平台不一样下拉刷新效果,那么你须要分别使用 RefreshIndicator
和 CupertinoSliverRefreshControl
作显示,否则多端都会呈现出一致的效果。
前面说过, Flutter 在不少方面都借鉴了 React Native ,因此在状态管理方面也极具“即视感”,好比都是调用 setState
的方式去更新,同时操做都不是当即生效的 ,固然它们也有着差别的地方,以下代码所示:
Component
内初始化一个 this.state
变量,而后经过 this.state.name
访问 。StatefulWidget
,而后在其的 State
对象内经过变量直接访问和 setState
触发更新。/// JS
this.state = {
name: ""
};
···
this.setState({
name: "loading"
});
···
<Text>this.state.name</Text>
/// Dart
var name = "";
setState(() {
name = "loading";
});
···
Text(name)
复制代码
固然它们二者的内部实现也有着很大差别,好比 React Native 受 React diff 等影响,而 Flutter 受 isRepaintBoundary
、markNeedsBuild
等影响。
而在第三方状态管理上,二者之间有着极高的类似度,如早期在 Flutter 平台就涌现了不少前端的状态管理框架如:flutter_redux 、fish_redux 、 dva_flutter 、flutter_mobx 等等,它们的设计思路都极具 React 特点。
同时 Flutter 官方也提供了 scoped_model 、provider 等具有 Flutter 特点的状态管理。
因此在状态管理上 React Native 和 Flutter 是十分相近的,甚至是在跟着 React 走。
在跨平台开发中,就不得不说到接入原有平台的支持,好比 在 Android 平台上接入 x5 浏览器 、接入视频播放框架、接入 Lottie 动画框架等等。
这一需求 React Native 先天就支持,甚至在社区就已经提供了相似 lottie-react-native 的项目。 由于 React Native 整个渲染过程都在原生层中完成,因此接入原有平台控件并不会是难事 ,同时由于发展多年,虽然各种第三方库质量良莠不齐,可是数量上的优点仍是很明显的。
而 Flutter 在就明显趋于弱势,甚至官方在开始的时候,连 WebView
都不支持,这其实涉及到 Flutter 的实现原理问题。
由于 Flutter 的总体渲染脱离了原生层面,直接和 GPU 交互,致使了原生的控件没法直接插入其中 ,而在视频播放实现上, Flutter 提供了外界纹理的设计去实现,可是这个过程须要的数据转换,很明显的限制了它的通用性, 因此在后续版本中 Flutter 提供了 PlatformView
的模式来实现集成。
以 Android 为例子,在原生层 Flutter 经过
Presentation
副屏显示的原理,利用VirtualDisplay
的方式,让 Android 控件在内存中绘制到Surface
层。VirtualDisplay
绘制在Surface
的 textureId ,以后会通知到 Dart 层,在 Dart 层利用AndroidView
定义好的Widget
并带上 textureId ,那么 Engine 在渲染时,就会在内存中将 textureId 对应的数据渲染到AndroidView
上。
PlatformView
的设计一定致使了性能上的缺陷,最大的体现就是内存占用的上涨,同时也引导了诸如键盘没法弹出#19718和黑屏等问题,甚至于在 Android 上的性能还可能不如外界纹理。
因此目前为止, Flutter 原生控件的接入上是仍不如 React Native 稳定。
React Native 和 Flutter 都是支持插件开发,不一样在于 React Native 开发的是 npm 插件,而 Flutter 开发的是 pub 插件。
React Native 使用 npm 插件的好处就是:可使用丰富的 npm 插件生态,同时减小前端开发者的学习成本。
可是使用 npm 的问题就是太容易躺坑,由于 npm 包依赖的复杂度和深度所惑,以致于你均可能不知道 npm 究竟装了什么东西,抛开安全问题,这里最直观的感觉就是 :“为何别人跑得起来,而个人跑不起来?” 同时每一个项目都独立一个 node_module ,对于硬盘空间较小的 Mac 用户略显心酸。
Flutter 的 pub 插件默认统一管理在 pub 上,相似于 npm 一样支持 git 连接安装,而 flutter packages get
文件通常保存在电脑的统一位置,多个项目都引用着同一份插件。
- win 通常是在 C:\Users\xxxxx\AppData\Roaming\Pub\Cache 路径下
- mac 目录在 ~/.pub-cache
若是找不到插件目录,也能够经过查看 .flutter-plugins
文件,或以下图方式打开插件目录,至于为何须要打开这个目录,感兴趣的能够看看这个问题 13# 。
最后说一下 Flutter 和 React Native 插件,在带有原生代码时不一样的处理方法:
React Native 在安装完带有原生代码的插件后,须要执行 react-native link
脚本去引入支持,具体如 Android 会在 setting.gradle
、 build.gradle
、MainApplication.java
等地方进行侵入性修改而达到引用。
Flutter 则是经过 .flutter-plugins
文件,保存了带有原生代码的插件 key-value 路径 ,以后 Flutter 的脚本会经过读取的方式,动态将原生代码引入,最后经过生成 GeneratedPluginRegistrant.java
这个忽略文件完成导入,这个过程开发者基本是无感的。
因此在插件这一块的体验, Flutter 是略微优于 React Native 的。
React Native 编译后的文件主要是 bundle
文件,在 Android 中是 index.android.bunlde
文件,而在 IOS 下是 main.jsbundle
。
Flutter 编译后的产物在 Android 主要是 :
isolate_snapshot_instr
应用程序指令段isolate_snapshot_data
应用程序数据段vm_snapshot_data
虚拟机数据段vm_snapshot_instr
虚拟机指令段等产物⚠️注意,1.7.8 以后的版本,Android 下的 Flutter 已经编译为纯 so 文件。
在 IOS 主要是 App.framework ,其内部也包含了 kDartVmSnapshotData
、kDartVmSnapshotInstructions
、 kDartIsolateSnapshotData
、kDartIsolateSnapshotInstructions
四个部分。
接着看完整结果,以下图所示,是空项目下 和 GSY 实际项目下, React Native 和 Flutter 的 Release 包大小对比。
能够看出在 React Native 同等条件下, Android 比 IOS 大不少 ,这是由于 IOS 自带了 JSCore ,而 Android 须要各种动态 so 内置支持,并且这里 Android 的动态库 so 是通过了 ndk
过滤后的大小,否则还会更大。
Flutter 和 React Native 则是相反,由于 Android 自带了 skia ,因此比没有自带 skia 的 IOS 会小得多。
以上的特色在 GSY 项目中的 Release 包也呈一样状态。
类型 | React Native | Flutter |
---|---|---|
空项目 Android |
![]() |
![]() |
空项目 IOS |
![]() |
![]() |
GSY Android |
![]() |
![]() |
GSY IOS |
![]() |
![]() |
值得注意的是,Google Play 最近发布了 《8月不支持 64 位,App 将没法上架 Google Play!》 的通知 ,同时也表示将中止 Android Studio 32 位的维护,而 arm64-v8a
格式的支持,React Native 须要在 0.59 之后的版本才支持。
至于 Flutter ,在打包时经过指定 flutter build apk --release --target-platform android-arm64
便可。
说到性能,这是一个你们都比较关心的概念,可是有一点须要注意,抛开场景说性能显然是不合适的,由于性能和代码质量与复杂度是有必定联系的。
先说理论性能,在理论上 Flutter 的设计性能是强于 React Native ,这是框架设计的理念致使的,Flutter 在少了 OEM Widget ,直接与 CPU / GPU 交互的特性,决定了它先天性能的优点。
这里注意不要用模拟器测试性能,特别是IOS模拟器作性能测试,由于 Flutter 在 IOS模拟器中纯 CPU ,而实际设备会是 GPU 硬件加速,同时只在 Release 下对比性能。
代码的实现方式不一样,也可能会致使性能的损失,好比 Flutter 中 skia 在绘制时,
saveLayer
是比较消耗性能的,好比 透明合成、clipRRect 等等,都会可能须要saveLayer
的调用, 而saveLayer
会清空GPU绘制的缓存,致使性能上的损耗,从而致使开发过程当中若是掉帧严重。
最后以下图所示,是去年闲鱼用 GSY 项目作测试对比的数据,原文在《流言终结者- Flutter和RN谁才是更好的跨端开发方案?》 ,能够看出在去年的时候, Flutter的总体帧率和绘制就有了明显的优点。
额外补充一点,JS 和 Dart 都是单线程应用,利用了协程的概念实现异步效果,而在 Flutter 中 Dart 支持的
isolate
,倒是属于完彻底全的异步线程处理,能够经过 Port 快捷地进行异步交互,这大大拓展了 Flutter 在 Dart 层面的性能优点。
以前一篇 《为何 Airbnb 放弃了 React Native?》 文章,让众多不明因此的吃瓜群众觉得 React Native 已经被放弃,以后官方发布的 《Facebook 正在重构 React Native,将重写大量底层》 公示,又再一次稳定了军心。
同时 React Native 在 0.59 版本开始支持 React Hook 等特性,并将本来平台的特性控件从 React Native 内部剥离到社区,这样控件的单独升级维护能够更加便捷,同时让 React Native 与 React 之间的界限愈加模糊。
Flutter UI 平台的无关能力,让 Flutter 在跨平台的拓展上更为迅速,尽管 React Native 也有 Web 和 PC 等第三方实现拓展支持,可是因为平台关联性太强,这些年发展较为缓慢, 而 Flutter 则是短短期又宣布 Web 支持,甚至拓展到 PC 和嵌入式设备当中。
这里面对于 Flutter For Web 相信是你们最为关心的话题, 以下图所示,在 Flutter 的设计逻辑下,开发 Flutter Web 的过程当中,你甚至感知不出来你在开发的是 Web 应用。
Flutter Web 保留了 大量本来已有的移动端逻辑,只是在 Engine 层利用 Dart2Js 的能力实现了差别化, 不过现阶段而言,Flutter Web 仍处在技术预览阶段,不建议在生产环境中使用 。
由此能够推测,无论是 Flutter 或者 React Native,都会努力将本身拓展到更多的平台,同时在本身的领域内进一步简化开发。
《Facebook 正在重构 React Native,将重写大量底层》
《React Native 的将来与React Hooks》
《庖丁解牛!深刻剖析 React Native 下一代架构重构》
自此,本文终于结束了,长呼一口气。