React Native --踩坑記 之 建立指定 React Native版本的專案

_夏霂熠雨發表於2018-12-03

前 言

最近一段時間一直在寫 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 --踩坑記 之 建立指定 React Native版本的專案

React Native --踩坑記 之 建立指定 React Native版本的專案

果然如此~~

然後終於理解了,react-native 中文網 中提示如果建立指定版本,版本號必須滿足兩位小數點 這句話是為什麼了。

React Native --踩坑記 之 建立指定 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…

相關文章