做爲 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 下一代架構重構》
自此,本文終於結束了,長呼一口氣。