前 言
最近一段時間一直在寫 RN 的專案,期間遇到了挺多的坑,然後想著記錄一下填坑的過程(想看答案的小夥伴可以忽略我的心厲路程,直接跳到結尾總結處)。
step1. 我竟然偷偷的給自己挖了個坑?
於是乎,第一步,趕緊新建一個demo,飛快地在 terminal 中輸入 react native init yx_rnDemo
,漫長的等待後,專案成功建立。 然後用 IDE 開啟 demo ,執行react-native run-android
命令,結果半路夭折,沒跑起來。仔細一看錯誤日誌,發現 android 各種依賴都下載失敗。然後看了下 package.json
中 react-native 的版本,發現引用的是最新版本,然後點選檢視 android 資料夾,發現引用的 gradle 版本是 3.1.4 ,然鵝我用的還是 2.3.3 的版本。。
因為比較懶(這句話在我的部落格中出現的次數不低,懶是萬惡之源,罪過罪過~~),不想升級,再配置一系列東西,所以按照 中文網 給出的建立指定版本的方法:
提示:你可以使用--version引數(注意是兩個槓)建立指定版本的專案。例如react-native init MyApp --version 0.44.3。注意版本號必須精確到兩個小數點
刪掉 demo ,重新輸入 react native init yx_rnDemo --version0.47.2
,結果最後發現其實建立的還是最新版。。(內心 OS,what??其實細心的朋友估計已經發現問題了,哈哈,噓~~)
然後開始各種面向搜尋引擎,發現大家都是這樣建立的啊,並且 RN 官網上給出的命令也是這樣的,為什麼別人沒有問題,到我這就有問題了呢。
然後換了一個命令執行: react-native init yx_rnDemo --verbose --version 0.47.2
想來看一下建立專案的詳細資訊,結果最後顯示建立的竟然是對的!! 就是 0.47.2 。
難道說加了一個 --verbose
條件就能建立成功了?不對啊,我看了下說 --verbose
條件只是會輸出詳細資訊的啊,照理說不應該對結果產生什麼影響的。然後不信邪的我把 --verbose
命令去掉,又執行了react-native init yx_rnDemo --version 0.47.2
,過了一會發現,竟然也是對的!!
嚇得我趕緊去翻我第一次寫的命令,一對比,發現我第一個命令--verison
後沒有換行~~
第一次的:react native init yx_rnDemo --version0.47.2 第二次的:react-native init yx_rnDemo --version 0.47.2
本來到這就可以結束的,然而作為一個想有靈魂的程式猿,還是很想弄清楚為什麼不加空格就會建立最新版本,而不是提示語法錯誤的原因。
step2. 一步步分析坑是如何產生的
這裡首先介紹一下, react-native 原始碼中 react-native-cli 資料夾下的 index.js 這個檔案很重要,它唯一的工作是初始化儲存庫,然後將所有命令轉發到本地的 react-native 版本。所以我們初始化專案時做的操作可以在這個檔案中找到。
開啟這個 js 檔案,然後開始一探究竟吧。 1.
'use strict';
var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
var execSync = require('child_process').execSync;
var chalk = require('chalk');
var prompt = require('prompt');
var semver = require('semver');
/**
* Used arguments:
* -v --version - to print current version of react-native-cli and react-native dependency
* if you are in a RN app folder
* init - to create a new project and npm install it
* --verbose - to print logs while init
* --template - name of the template to use, e.g. --template navigation
* --version <alternative react-native package> - override default (https://registry.npmjs.org/react-native@latest),
* package to install, examples:
* - "0.22.0-rc1" - A new app will be created using a specific version of React Native from npm repo
* - "https://registry.npmjs.org/react-native/-/react-native-0.20.0.tgz" - a .tgz archive from any npm repo
* - "/Users/home/react-native/react-native-0.22.0.tgz" - for package prepared with `npm pack`, useful for e2e tests
*/
var options = require('minimist')(process.argv.slice(2));
複製程式碼
檔案的前 62 行都是在宣告變數及引用,其中有幾個變數這裡我們需要知道它們是的作用:
變數名 | 含義 |
---|---|
fs | Node.js 中 檔案系統操作的模組 |
path | Node.js 中用於處理檔案路徑的小工具的模組 |
exec | Node.js 中子程式模組, 衍生一個 shell 並在 shell 上執行命令 |
execSync | exec 的同步函式,會阻塞 Node.js 事件迴圈 |
chalk | 定製控制檯日誌的輸入樣式的一個外掛 |
prompt | node 命令列輸入控制元件 |
semver | semver 語義化版本號 |
options | 輕量級的命令列引數解析工具 |
其中一個很關鍵的變數 options ,也就是引用的require('minimist')(process.argv.slice(2))
,是一個命令列引數解析工具,具體的介紹可以參考這裡,它是以鍵值對進行解析的。比如我們輸入的命令列是:react-native init yx_rnDemo --version 0.47.2
,其中 --version 0.47.2
就是一個可解析的鍵值對,key 為 version , value 為 0.47.2 。
這個檔案中,我們有用到的鍵值對的值在截圖的註釋中可以看到:
-v
: 列印當前 react-native-cli 的版本和 react native 的依賴關係init
: 建立一個新工程並且執行 npm install--verbose
:init
時新增的引數,列印init
時的引數- -
-template
:用到的模板的名稱 --version
: 會覆蓋預設(最新版本)安裝的 react-native 的版本。 也就是如果要建立指定版本的,需要加上這個引數
OK, 各個變數的含義我們都弄清楚了,下面讓我們繼續探究~~
2.
switch (commands[0]) {
case 'init':
if (!commands[1]) {
console.error('Usage: react-native init <ProjectName> [--verbose]');
process.exit(1);
} else {
init(commands[1], options);
}
break;
default:
//...程式碼省略
break;
}
}
複製程式碼
在這之前 116 行 定義了 commands 這個變數,取值的結果是解析的引數,應該是 _ [ 'init ', 'yx_rnDEmo']
,所以會走switch 的第一個選項,去執行 init(commands[1], options)
方法,引數為 'yx_rnDemo’ 和 options 變數。
3.
/**
* @param name Project name, e.g. 'AwesomeApp'.
* @param options.verbose If true, will run 'npm install' in verbose mode (for debugging).
* @param options.version Version of React Native to install, e.g. '0.38.0'.
* @param options.npm If true, always use the npm command line client,
* don't use yarn even if available.
*/
function init(name, options) {
validateProjectName(name);
if (fs.existsSync(name)) {
createAfterConfirmation(name, options);
} else {
createProject(name, options);
}
}
複製程式碼
很簡單,先去判斷我們起的工程名稱是否符合命名規範,並且判斷是否存在。所以下面直接看 createProject(name, options)
方法
4.
function createProject(name, options) {
//....程式碼省略
run(root, projectName, options);
}
複製程式碼
這個方法裡主要是去進行建立工程資料夾和 package.json 檔案的操作,然後後續行動在run(root, projectName, options)
函式中
5.
function run(root, projectName, options) {
var rnPackage = options.version; // e.g. '0.38' or '/path/to/archive.tgz'
console.log('Installing ' + getInstallPackage(rnPackage) + '...');
//...程式碼省略
installCommand = 'npm install --save --save-exact ' + getInstallPackage(rnPackage);
if (options.verbose) {
installCommand += ' --verbose';
}
//...程式碼省略
try {
execSync(installCommand, {stdio: 'inherit'});
} catch (err) {
//... 程式碼省略
}
cli.init(root, projectName);
}
複製程式碼
其中這個 rnPackage
就是解析的 version 引數 ,所以,對於我的第一次使用的命令:react-native init yx_rnDemo --version0.47.2
來說,解析工具並沒有找到 key 為 version 的引數,所以第一次命令的 rnPackage
的值應該是空的,輸入正確後就是 0.47.2 了。
然後看 installCommand
這個變數,就是最終執行的命令。其中一個引數是需要到getInstallPackage(rnPackage)
去確定一下是什麼。
function getInstallPackage(rnPackage) {
var packageToInstall = 'react-native';
var isValidSemver = semver.valid(rnPackage);
if (isValidSemver) {
packageToInstall += '@' + isValidSemver;
} else if (rnPackage) {
// for tar.gz or alternative paths
packageToInstall = rnPackage;
}
return packageToInstall;
}
複製程式碼
OMG! 看到上面的程式碼 激不激動,終於真相大白了!! 按照我的第一次錯誤的寫法,這個 rnPackage 是空,然後
var isValidSemver = semver.valid(rnPackage);
複製程式碼
這一行程式碼的含義是進行一個版本語義化規範的檢查,就是你建立的版本號必須符合 semver語義化規範,也就是 x.y.z 的格式,比如 0.47.2 ,然而我現在傳的空,肯定是不符合規範的,果斷返回 false ,所以該方法會返回 "react-native", 預設會安裝最新版。 而我後來正確的寫法,是符合規範的,最終該方法會返回 "react-native@0.47.2" ! 然後就會下載指定的版本了。
最後我們這邊可以驗證下,輸出的 log 引數是不是我們在程式碼中看到的。
上圖:
果然如此~~
然後終於理解了,react-native 中文網 中提示如果建立指定版本,版本號必須滿足兩位小數點 這句話是為什麼了。
step3. 總結 & 填坑
所以說了那麼多,如果想在 init 時候指定版本號,非常簡單,,就是官網指出的:
react-native init MyApp --version 0.44.3
但必須注意檢查兩點(估計也就我這麼粗心的人會犯吧):
1.--version
一定要加空格,千萬不要寫成 --version0.44.3
2.版本號一定要兩位小數點,必須符合 semver語義化規範
參考文章
github.com/facebook/re… nodejs.cn/api/child_p… www.runoob.com/nodejs/node… nodejs.cn/api/fs.html… www.jianshu.com/p/231b931ab…