React Native --踩坑记 之 创建指定 React Native版本的项目

前 言

最近一段时间一直在写 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @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.

1
2
3
4
function createProject(name, options) {
//....代码省略
run(root, projectName, options);
}

这个方法里主要是去进行创建工程文件夹和 package.json 文件的操作,然后后续行动在run(root, projectName, options) 函数中

5.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)去确定一下是什么。

1
2
3
4
5
6
7
8
9
10
11
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 是空,然后

1
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语义化规范

参考文章

https://github.com/facebook/react-native
http://nodejs.cn/api/child_process.html
http://www.runoob.com/nodejs/nodejs-path-module.html
http://nodejs.cn/api/fs.html#fs_file_system
https://www.jianshu.com/p/231b931ab389


------------- 本文结束 感谢您的阅读 -------------