React Native 做爲一款跨端框架,有一個最讓人頭疼的問題,那就是版本更新。尤爲是遇到大版本更新,JavaScript、iOS 和 Android 三端的配置構建文件都有很是大的變更,有時候三者的配置文件又互相耦合在一塊兒,每每牽一髮而動全身。javascript
本文假定 React Native 升級的主導者是前端同窗,比較熟悉 javaScript 爲主的一套前端構建流程。若是有條件,升級時強烈建議拉上 iOS 和 Android 開發,對於一些瑣碎的升級細節,當面溝通遠比搜索引擎高效。html
提示:由於每次修改和新增內容都會隱藏文章從新審覈,建議閱讀博客原文得到最佳閱讀體驗
👉 閱讀博客原文前端
以爲文章對你有用的話必定要記得點贊哦 🌟,謝謝你,這對我來講真的很重要!java
這部分知識我認爲是最重要的,畢竟版本更新是永恆的,操做流程倒是不變的。node
詳細介紹各端構建工具前,咱們拋開各類技術細節,從整個項目的生命週期出發,看看大部分產品是怎麼作技術規劃的:react
理清一個技術產品的生命週期後,你就會對平常開發中配置文件有了總體的認知:那些又臭又長的配置項,亂七八糟的兼容寫法,毫無美感的 DSL,最神奇的是這些七拼八湊的東西還能把項目跑起來,Build 成功的那一刻你必定會對這種人類奇蹟發出由衷的敬佩之情——原來這就叫專業啊!android
收一收澎湃的情緒,牢記上面的指導經驗,咱們下面開始討論技術細節。webpack
前端工程化一直是前端裏面的熱點,雖然一直很熱,可是具體實現仍是一團糟。我的認爲緣由主要有兩點,一個是前端構建從無到有,相對而言基礎薄弱;一個是社區推進,百花齊放的同時又沒有統一標準。就拿如今前端的主要配置文件來講:ios
package.json
管理 npm 包package.json
裏.eslintrc.js
babel.config.js
上面只是列出了幾個主流配置,不出意外的話,如今你的項目裏已經有 5 個配置文件了,在 JavaScript 這個前端萬能腳本語言的粘合下,這些配置文件還能夠互相引用互相耦合,複雜度搞成這樣,開發體驗尚未 iOS Android 的一半好。git
若是你認爲我只是單純的批評前端那你就理解錯了,我想表達的是,這麼複雜的配置都能搞定,iOS Android 的項目配置還不是手到擒來?
iOS 項目主要有兩個點:project.pbxproj 和 CocoaPods。這兩塊兒的知識瞭解後,升級 RN 就徹底不虛了。
project.pbxproj
就是一個 iOS 項目的配置文件,從數據結構特色上有些像 JSON,年齡能夠追溯到 NeXT,可讀性基本爲 0,每次 git 合併都是純黑的噩夢。不信你瞅瞅下圖,這是給人看的嗎。
可讀性這麼差的東西能傳下來,其實全靠 XCode 這個 IDE 給它續命。咱們每次在 XCode 裏修改的配置,例如 Build Settings
等選項,最後都會反映到 project.pbxproj
這個配置文件上,也算是一種另類 DSL 了。
project.pbxproj
相關的知識我推薦下面幾篇文章,閱讀後會讓你對 iOS 編譯打包流程有個更深的瞭解:
project.pbxproj
文件的一些特色project.pbxproj
文件的對應關係有着更深入的瞭解CocoaPods 是一個負責管理 iOS 項目中第三方開源庫的工具,目前主流 iOS 工程都是用 CocoaPods 管理第三方庫的。
React Native 在 0.60 裏終於用上了 CocoaPods,和 iOS 社區步調一致了起來。這樣作的好處就是後續維護和迭代的壓力會小不少,鬼知道我之前升級各類 iOS SDK 的日子是怎麼熬過來的。
相對 project.pbxproj
,CocoaPods 無疑簡單了很多,寫配置腳本的 Ruby 語言也比較清爽,Podfile 的可讀性要高不少。
platform :ios, '9.0' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' target '項目名稱' do pod 'React', :path => '../node_modules/react-native/' pod 'React-Core', :path => '../node_modules/react-native/React' use_native_modules! end
CocoaPods 的學習資料能夠參考下文,不夠的話自行搜索便可:
Android 的項目配置主要是由 gradle 文件控制的,gradle 文件又由 Groovy 這門 JVM 系的腳本語言書寫。到這裏思路就很明顯了,咱們只要瞭解一些 Groovy 的語法和 gradle 的寫法,就能讀懂和修改 Android 的配置文件了。在這裏我推薦一些相關教程,讀完後就會有個大體的瞭解:
學習了基礎的語法後,再回到 Android 工程上來。Android 的項目配置主要由 3 個文件控制,升級時衝突較多的也是這 3 個文件:
settings.gradle
:用來指示 Gradle 在構建應用時應將哪些模塊包含在內build.gradle
:定義適用於項目中全部模塊的構建配置app/build.gradle
:定義 App 的構建配置我的認爲 Android 的 Gradle 配置仍是比較容易入門的,由於 gradle 文件有個好處,能夠隨意的添加註釋。你們能夠花點兒時間把每一個配置項都加上註釋,這樣在升級改動過程當中就不容易發怵。
React Native 官方在 2019 年 7 月 0.60 大版本更新時,推出了 Upgrade Helper 這個 Diff 小工具。經過這個工具咱們能夠方便的看出版本更新時各個配置腳本的改動,很是的方便。
RN 版本升級時,個人升級流程通常是這樣的:
README.md
文件,是否須要同步升級在更新過程當中,我的建議 git commit 操做要儘可能原子化,方便後續覆盤和回滾,當心駛得萬年船。
在我實際升級中,由於 React Native 0.59 到 0.60 有很是大的變更,而且業務較爲複雜,升級 0.60 花了兩個星期的時間:iOS 一週,Android 一週;0.61 和 0.62 的升級就比較簡單了,大概一兩個小時就能夠升級好。
2019 年 7 月 3 日 Facebook 官方發佈了 React Native 0.60,這是一次很是大的版本更新,雖然沒有添加新的功能,可是在底層上作了不少優化,向主流配置靠齊:
react-native link *
了0.60 升級時必定要有耐心,不可能一次性成功的,建議參考 Upgrade Helper 和 Upgrade to React Native 0.60 這篇博文,我會對文中沒有說明的地方進行補充。
升級前先確保相關第三方包已是最新版本。
JavaScript 這裏相對來講好升級一些,畢竟是前端程序員的主場。根據 Diff 差別升級版本號後,還須要注意如下幾點:
NetInfo、WebView 和 Geolocation 從 React Native 中移除,交給 react-native-community 社區維護。因此咱們須要修改 import
時的路徑。
Slider、AsyncStorage、CameraRoll、Clipboard 等組件也有移除計劃,此次升級也能夠順便遷移一下。
值得注意的是,react-native-webview 在一次更新中爲了響應 App Store 政策,已經移除了 UIWebView,只支持 WKWebView。若是你作過移動端的適配,你確定明白 WKWebview 對 cookie 支持不太友好,這裏須要重點回歸測試一下;另一點是若是 RN 和 H5 網頁是經過 postMessage
的方式交互,相關 API 也有一些不兼容更新,這裏須要重點適配一下,具體細節能夠看文檔。
SwipeableFlatList 是 React Native 在 0.5X 某個版本提供的側滑刪除列表組件,雖然一直沒有官方文檔中放出來,可是社區上已經有不少人在使用了。可能對這個組件的實現不太滿意,官方在 0.60 裏刪除了這個組件。爲了避免讓項目報錯,咱們可能須要把 SwipeableFlatList 相關的源碼拿出來本身手動維護一下,有人把相關代碼提出來維護了一個 npm 包——react-native-swipeable-lists,你們能夠引入暫時過分一下。
0.60 版本的 React Native 支持 CocoaPods,2020 年了,RN 終於支持 CocoaPods 了,沒有 CocoaPods 的時代,爲了使用一些 iOS 第三方庫,咱們必須手動把庫文件拖到主工程裏,升級和維護很是不方便。由於 0.61 版本 CocoaPods 是惟一可選包管理方案,因此強烈建議直接升級使用。
遷移 CocoaPods 前,先在 CLI 裏輸入一下命令 unlink Native Modules:
react-native unlink
unlink 後就要遷移到 CocoaPods 了。遷移前確保 Ruby 和 CocoaPods 已經安裝成功,具體的安裝過程不是本文重點就不展開了,沒有安裝的同窗自行 Google 搜索。
咱們在 ios 目錄裏新建一個文件 Podfile
,在裏面輸入如下代碼:
platform :ios, '9.0' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' target '項目名稱' do pod 'React', :path => '../node_modules/react-native/' pod 'React-Core', :path => '../node_modules/react-native/React' pod 'React-DevSupport', :path => '../node_modules/react-native/React' pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' pod 'React-RCTWebSocket', :path => '../node_modules/react-native/Libraries/WebSocket' pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga' pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' target '項目名稱Tests' do inherit! :search_paths # Pods for testing end use_native_modules! end
上面這段代碼,pod
開頭的都是從 node_modules
目錄導入 react-native
相關的官方代碼。下面兩行代碼是實現 autolink 的功能:
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' target '項目名稱' do ... use_native_modules! end
Podfile
配置好後,就在 ios 文件夾下運行 pod install
,安裝相關依賴。
安裝成功後會生成一個 xcworkspace 空間,這時候你須要退出當前的 xcodeproj 項目,打開 xcworkspace。
在 xcworkspace 裏,首先有兩個頂層文件夾,一個是你的 xcodeproj 項目,一個是 Pods 文件夾(左圖):前者包含着你的業務代碼,後者管理者安裝的第三方庫文件。這時候須要手動把 你的項目/Libraries
目錄下的 *.xcodeproj
文件手動刪除(右圖紅框 ➊),由於他們已經存在於 Pods 文件夾裏了(右圖紅框 ➋)。
上一步修改了 React Native 項目的引用方式,但還有一個問題,那就是尋址的頭文件路徑並無修改過來,咱們能夠觀察下面兩張圖:
$(SRCROOT)/../node_modules/*
$(PODS_CONFIGURATION_BUILD_DIR)/*
當時這個變化卡了我一天,並且這個變化是在 project.pbxproj
中的,很是難以閱讀就忽略掉了。後來經過新建一個 RN 新項目發現了問題。解決方法是刪除原來的 Header Search Path 內容,手動把新的路徑添加進去。
上面兩步作完後能夠嘗試 build 一下項目,大機率你會發現仍是 build 不起來。由於錯誤緣由千奇百怪我也沒法一一覆蓋,這裏仍是問 Google 比較方便。
到這一步假設你已經 Build 起來 iOS 項目了,這時候你會發現一個問題,以前 iOS build 成功後,會自動啓動一個 node 服務器編譯 javascript 文件,更新後並無自動啓動 node 服務器,須要咱們手動 npm run start
啓動 node 服務器,很是的不方便。
問題出在哪裏呢?緣由是在原來的構建方式裏,Libraries 下的 React.xcodeproj
有個 Start Packager 腳本,這個腳本會在項目 build 成功後自動啓動一個 node 服務器:
遷移到 Pods 後,這個腳本就沒有了,須要咱們在主工程裏手動添加一下。添加方式也很簡單,我在下圖也標註好了,點擊項目文件夾,在 TARGETS
的 Build Phases
裏點擊 ➕,再點擊 New Run Script Phase
新增一個腳本區域,而後把下面的代碼填寫進去:
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}" echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${SRCROOT}/../node_modules/react-native/scripts/.packager.env" if [ -z "${RCT_NO_LAUNCH_PACKAGER+xxx}" ] ; then if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly" exit 2 fi else open "$SRCROOT/../node_modules/react-native/scripts/launchPackager.command" || echo "Can't start packager automatically" fi fi
這個 Start Packager 腳本的位置也有些講究,最好放在 Check Pods Manifest.lock
和 Compile Sources
之間,要否則啓動 node 服務器時會致使報錯。
隨着 iPhone 產品線的增多,iPhone手機的尺寸也多了起來,原來一個尺寸配一個 LaunchImage
的方式逐漸變的再也不適用,這時候 官方建議用 LaunchScreen.storyboard
來製做啓動屏,而且要求 2021 年全部 APP 都得改成此方案。
具體的配置網上有不少教程了,你們搜索參考配置就好。我我的參考瞭如下教程:
若是項目以前有配置過自動打包腳本,由於此次升級遷移到 workspace,因此也得對原來的打包腳本作一些修改:
xcodebuild archive -project 項目名稱.xcodeproj
⬇️
xcodebuild archive -workspace 項目名稱.xcworkspace
關於 xcodebuild 能夠參考這兩篇文章:
0.60 的 Android 更新主要是 3 點:
升級前先須要升級 Gradle 和 Groovy 的版本。具體細節參考 Upgrade Helper。
AndroidX 的推動主要是 Google 官方受夠了 Android 目前混亂不堪的 android.support
,用一個統一的 androidx
來代替。升級跟着 Android 官方文檔走就行,我主要參考瞭如下文檔:
遷移工做主要是修改 import
路徑,工做量可能有些大,但心理負擔較小,本質上就是改了個名字,問題不大。
Autolinking 功能集成前先試試運行 react-native unlink
,看看能不能自動取消連接。若是取消失敗,就要本身手動刪除舊的 link 代碼,加入新的 Autolinking 代碼。下面我以 react-native-svg 這個第三方庫爲例進行說明:
1.檢查 android/settings.gradle
,刪除舊的 include
配置,加入下面新的代碼:
rootProject.name = '你的項目' - include ':react-native-svg' - project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') + apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app'
2.檢查 android/app/build.gradle
,刪除舊的配置,文件的最後一行加入一行配置:
dependencies { - implementation project(':react-native-svg') } + apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
3.檢查 MainApplication.java
,刪除舊的引用:
- @Override - protected List<ReactPackage> getPackages() { - return Arrays.<ReactPackage>asList( - new MainReactPackage(), - new SvgPackage() - ); + @SuppressWarnings("UnnecessaryLocalVariable") + List<ReactPackage> packages = new PackageList(this).getPackages(); + return packages; - }
值得注意的是,咱們業務中頗有可能會本身封裝一些 Native Module,通過上面的修改後,導入 Native Module 的方式也要作相應的修改,這裏能夠參考官方文檔 Android Register the Module:
+ import com.your-app-name.CustomToastPackage; // <-- Add this line with your package name. protected List<ReactPackage> getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List<ReactPackage> packages = new PackageList(this).getPackages(); + packages.add(new CustomToastPackage()); // <-- Add this line with your package name. return packages; }
Hermes 是一個 Facebook 開源的 Javascript 引擎,和如今的 JSC 相比,在包體積和啓動速度上有所優化。社區上已經有不少介紹 Hermes 的文章了,我找了幾篇比較好的,若是對 Hermes 感興趣能夠移步查看。
Hermes 的相關特性不是本文重點,因此就很少介紹了。
Android 想要使用 Hermes 的話,必須得使用版本號大於 0.60.4 的 React Native,而且要對 android/app/build.gradle
作一些修改:
project.ext.react = [ - entryFile: "index.js" + entryFile: "index.js", + enableHermes: false, // clean and rebuild if changing ] - def useIntlJsc = false + def jscFlavor = 'org.webkit:android-jsc:+' dependencies { - if (useIntlJsc) { - implementation 'org.webkit:android-jsc-intl:+' - } else { - implementation 'org.webkit:android-jsc:+' - } + if (enableHermes) { + def hermesPath = "../../node_modules/hermesvm/android/"; + debugImplementation files(hermesPath + "hermes-debug.aar") + releaseImplementation files(hermesPath + "hermes-release.aar") + } else { + implementation jscFlavor + } }
上面只列出了主要變動,若是不想用 Hermes,能夠徹底不作更改;若是想要嘗試一下,最好仍是根據 Upgrade Helper 列出的詳細變動進行修改,而後閱讀 React Native 官網的 Using Hermes 進行配置與調試。
React Native 0.61 最主要的更新就是 Fast Refresh 的引入了,這個功能大大提高了開發體驗。
Fast Refresh 的加入有兩個好處,第一個是把 live reloading 和 hot reloading 兩個功能合二爲一併作了功能增強;第二個終於支持 Hooks 熱更新了。雖然 0.59.10 已經支持 hooks,可是當時的函數式組件不支持熱更新,開發體驗過於差勁。升級到 React Native 0.61 後就可使用了。
總體來講 0.61 的更新很小,一兩個小時就能夠完成升級。升級前建議參考 Upgrade Helper 和 Upgrade to React Native 0.61 這篇博文,我會對文中沒有說明的地方進行補充。
JavaScript 這裏主要是一些 API 的變更和升級,跟着報錯信息修改就好,難度並不大。
React 升級到 16.9 後,componentWillMount
等 API 廢棄,必須遷移到 UNSAFE_componentWillMount
等帶有 UNSAFE_
前綴的 API。
主工程裏這些 API 比較容易重構和替換,麻煩的是一些好久沒有維護的第三方 JS 包,這時候須要本身手動 Fork 一份代碼維護,或者替換同功能的正在維護的第三方包,這個屬於技術債,只能一點一點克服。
更新後有些方法和組件的引用路徑發生了變動,須要咱們適配一下:
1.ErrorUtils
默認綁定到 global 上,不須要 import ErrorUtils from ErrorUtils
導入了
2.RCTNetworking
引用路徑發生改變,須要修改成:
const RCTNetworking = require('react-native/Libraries/Network/RCTNetworking');
3.Dimensions
導入方式也發生了改變,須要修改:
import Dimensions from 'Dimensions';
⬇️
import { Dimensions } from 'react-native';
0.61 以後,React Native iOS 端只支持經過 Cocoapods Link 了,若是 0.60 已經升級到 Cocoapods 了,那麼此次的 iOS 升級將會很是快,只須要改動 Podfile 中一些庫的導入路徑就能夠了。
具體的差別可見 Upgrade Helper,很是簡單,比對修改後從新 pod install
就能夠了。
0.61 的 Android 升級也比較簡單,升級了 Gradle 版本,修改了 Hermes 的引用路徑,跟着 Upgrade Helper 的 Diff 依次修改就可。
React Native 0.62 也是增強了開發者體驗,RN 項目默認引入了 Flipper 這個 Facebook 製做的移動端調試工具,支持了 React DevTools v4,錯誤提示能夠選擇新的 LogBox,比原來的錯誤提示更加友好從而更容易定位問題。
除了開發體驗的增強,此次更新還支持了 Dark Mode 模式,RN 以後就能夠作暗黑模式的適配了。
總體來講 0.62 的更新也很小,一兩個小時就能夠完成升級。升級前建議參考 Upgrade Helper 和 Upgrade to React Native 0.62 這篇博文,我會對文中沒有說明的地方進行補充。
React Native 以前使用 Animated
API 時,useNativeDriver
默認值爲 false,也就是說默認都是 JS 線程繪製動畫。版本升級後須要顯式指定 useNativeDriver
的值。我認爲這個更新的意義在於每次使用 Animated
時,強迫開發者思考能不能讓動畫在 Native 線程運行,優化動畫體驗。
LogBox 這個功能在 0.62 裏是默認關閉的,0.63 版本默認開啓。0.62 裏開啓方式比較 Hack,須要按如下步驟操做:
1.項目根目錄新建一個 before.js
,而後裏面只寫一行代碼:
require('react-native').unstable_enableLogBox();
2.在 JS 全部文件的入口文件 index.js
的第一行裏導入這個文件:
import './before';
上面兩步必須嚴格執行,否則的話會有紅屏報錯。
Cocoapods 在這個版本里也有些改動,除去 Flipper 相關的 pod,改動很是小,根據 Upgrade Helper 中的 Diff 差別修改就好。
0.62 升級須要修改一些 Swift 相關的配置,具體升級流程可見 React Native 0.62 upgrade (Xcode)
0.61 的 Android 升級也比較簡單,升級了 Gradle 版本,除去 Flipper 相關的更新,改動很是小,跟着 Upgrade Helper 的 Diff 依次修改就可。
0.62 以後,Flipper 在 RN 的項目裏是默認添加的,能夠方便的查看 Layout、network 和 log 等信息。
舊項目升級時,Flipper 實際上是可選的,安裝有些波折,上手體驗了一下感受以下(版本爲 0.52.1):
console.log
信息和 Native 的 log 信息和在一個應用裏,比較方便查看React DevTools v4
,二者比對能夠方便查看佈局上面都是優勢,缺點仍是有很多的,下面我說說我用下來感受到的不足:
JSON.stringify
後的數據上面就是個人使用體驗,要不要在項目中使用,我以爲你們仍是親自體驗一下比較好。
若是要在項目中集成 Flipper,根據 Upgrade Helper 進行集成就好,難度不是很大。
上面就是 React Native 版本升級指南的內容了,本升級教程會持續更新,爲了得到最佳體驗能夠查看閱讀博客原文。
以爲文章對你有用的話必定要記得點贊哦 🌟,謝謝你,這對我來講真的很重要!
更多優秀文章推薦: