FerryVip's Blog

技术分享

pm2 node 服务器部署

安装

npm i -g pm2

启动服务

基本的 pm2 start app.js

带参数 pm2 ENV=production start app.js

从 .config.js 文件启动 pm2 start app.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
apps : [{
name : "worker",
script : "./worker.js",
watch : true,
env: {
"NODE_ENV": "development",
},
env_production : {
"NODE_ENV": "production"
}
},{
name : "api-app",
script : "./api.js",
instances : 4,
exec_mode : "cluster"
}]
}

从 json 文件启动 pm2 start app.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"apps": [
{
"name": "lost100-dev",
"script": "./scripts/server.js",
"watch": true,
"ignore_watch": [ "node_modules", "files", "public", "logs", "mongodb", "build", ".idea/", ".git"],
"max_memory_restart": "150M",
"instances": 2,
"exec_mode": "cluster",
"env": {
"NODE_ENV": "dev",
"PORT": 5566
}
}
]
}

可用的参数详情

General

Field Type Example Description
name (string) “my-api” application name (default to script filename without extension)
script (string) ”./api/app.js” script path relative to pm2 start
cwd (string) “/var/www/” the directory from which your app will be launched
args (string) “-a 13 -b 12” string containing all arguments passed via CLI to script
interpreter (string) “/usr/bin/python” interpreter absolute path (default to node)
interpreter_args (string) ”–harmony” option to pass to the interpreter
node_args (string) alias to interpreter_args

Advanced features

Field Type Example Description
instances number -1 number of app instance to be launched
exec_mode string “cluster” mode to start your app, can be “cluster” or “fork”, default fork
watch boolean or [] true enable watch & restart feature, if a file change in the folder or subfolder, your app will get reloaded
ignore_watch list [”[/\]./”, “node_modules”] list of regex to ignore some file or folder names by the watch feature
max_memory_restart string “150M” your app will be restarted if it exceeds the amount of memory specified. human-friendly format : it can be “10M”, “100K”, “2G” and so on…
env object {“NODE_ENV”: “development”, “ID”: “42”} env variables which will appear in your app
env_ object {“NODE_ENV”: “production”, “ID”: “89”} inject when doing pm2 restart app.yml –env
source_map_support boolean true default to true, [enable/disable source map file]
instance_var string “NODE_APP_INSTANCE” see documentation

Log files

Field Type Example Description
log_date_format (string) “YYYY-MM-DD HH:mm Z” log date format (see log section)
error_file (string) error file path (default to $HOME/.pm2/logs/XXXerr.log)
out_file (string) output file path (default to $HOME/.pm2/logs/XXXout.log)
combine_logs boolean true if set to true, avoid to suffix
merge_logs boolean true alias to combine_logs
pid_file (string) pid file path (default to $HOME/.pm2/pid/app-pm_id.pid)

Control flow

Field Type Example Description
min_uptime (string) min uptime of the app to be considered started
listen_timeout number 8000 time in ms before forcing a reload if app not listening
kill_timeout number 1600 time in milliseconds before sending a final SIGKILL
wait_ready boolean false Instead of reload waiting for listen event, wait for process.send(‘ready’)
max_restarts number 10 number of consecutive unstable restarts (less than 1sec interval or custom time via min_uptime) before your app is considered errored and stop being restarted
restart_delay number 4000 time to wait before restarting a crashed app (in milliseconds). defaults to 0.
autorestart boolean false true by default. if false, PM2 will not restart your app if it crashes or ends peacefully
cron_restart string “1 0 * * *” a cron pattern to restart your app. Application must be running for cron feature to work
vizion boolean false true by default. if false, PM2 will start without vizion features (versioning control metadatas)
post_update list [“npm install”, “echo launching the app”] a list of commands which will be executed after you perform a Pull/Upgrade operation from Keymetrics dashboard
force boolean true defaults to false. if true, you can start the same script several times which is usually not allowed by PM2

混合app集成热更新

原因是因为之前做的都是纯 rn 项目,偶尔接入原生,所以 codepush 比较好集成.

有一次在想如果原本是原生的项目,后面只是引入 rn 作为一些基础模块,这样的更新要如何做,也就有了这篇文章(技术很早实现了,博客因为懒后面盆友需要,拖了现在才写,也是尴尬);

1. 前提工作

你目前的 iOS 项目已经是 ReactNative 混合 App;
这边我用我之前的来做,具体代码可以看这个链接(国内Coding)或者这个链接(国外github);

2. 了解一些 codepush 的原理

之前在 CSDN 写的 codepush 相关的博客

CodePush 热更新ReactNative之CodePush CLI操作

CodePush 热更新ReactNative之React Native Client SDK

可以看下了解,当然最好去官方 gayhub 去看文档比较好;

按照文档吧 codepush 的环境安装好👌;

3. 集成进入 App 项目里

  1. 安装 codepush 模块 npm install --save react-native-code-push@latest;
  2. 在安装完成后,可以执行命令自动导包进去 react-native link react-native-code-push, 当然也可以手动导包,自己看下官方文档;
    在自动导包时,会要求你添加 codepush 的 key, 可以跳过下面会说到怎么添加;
  3. 到此就可以看到 package.json 里面多了一个模块;
    目录变化
    目录变化

原生项目文件变化,添加了 CodePush 工程,还有一些静态库,至于 AppDelegate.m 里面的好像是会有,有点忘记了;
原生项目文件变化

4. 配置 codepush 的 key

  1. codepush 创建 app code-push app add MyApp-iOS 会从中得到一个 key ,复制这个 key ,下一步在 iOS 原生中会用到;
    key
  2. iOS中原生配置 key ,在 Info.plist 文件中,如下图;

5. 原生代码配置

  1. AppDelegate.m 文件中配置,为了一进入 App 就加载好了, codepush 环境,我个人认为的,没有去测试;
  2. 在每处用到混合的地方加入代码,这边我用到了两处,所以两个的 .m 文件中,我都加入类似的代码;

6. js 代码的配置

js 基本的配置就是这些,具体的有些配置,可以官方文档看下;

7. iOS 打 release 包

  1. 在 js 主目录下,执行命令 curl http://localhost:8081/index.ios.bundle -o main.jsbundle 会在目录下有个 main.jsbundle 的文件,这个需要拖到 iOS 原生目录下,每次打包重新生成,都需要替换进去;
  2. iOS 打包 ipa 包,只要把刚才的 main.jsbundle 拖进去,剩下的步骤和正常打包一样;

8. 打 codepush 更新包

在 js 的目录下,执行命令 code-push release-react youAppName ios, 最简单命令,直接推到Staging;

9. 到此基本结束,根据你设置的更新条件, App 就会自动更新了;

如何在现有的iOS项目中添加ReactNative混合开发

如果公司的项目之前是用原生, 现在想做混合开发并且选择ReactNative, 这篇会是一个基础的环境配置;

1. 开发环境准备

首先, 至少你已经搭建了 React Native开发环境在iOS平台上所需的一切依赖软件(比如npm)。

其次, CocoaPods 包管理工具, 基本做iOS开发都有装, 如果没有, 那就百度一下😂;

2. 安装依赖包

React Native的植入过程同时需要React和React Native两个node依赖包。

在你的原有的iOS目录的上一级目录创建一个包管理package.json文件;

对于一个典型的React Native项目来说,一般package.json和index.ios.js等文件会放在项目的根目录下。而iOS相关的原生代码会放在一个名为ios/的子目录中,这里也同时放着你的Xcode项目文件(.xcodeproj)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// package.json 文件内容
{
"name": "NumberTileGame",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"react": "15.4.1",
"react-native": "0.39.2"
}
}

// 通过npm info react和npm info react-native 来查看当前的最新版本

npm install // 在 package.json 的当前目录下开启终端执行这段代码, 这些模块就会安装到 node_modules/ 下;

3. React Native框架嵌入iOS

Subspecs

在你开始把React Native植入到你的应用中之前,首先要决定具体整合的是React Native框架中的哪些部分。而这就是subspec要做的工作。在创建Podfile文件的时候,需要指定具体安装哪些React Native的依赖库。所指定的每一个库就称为一个subspec。

可用的subspec都列在node_modules/react-native/React.podspec中,基本都是按其功能命名的。一般来说你首先需要添加Core,这一subspec包含了必须的AppRegistry、StyleSheet、View以及其他的一些React Native核心库。如果你想使用React Native的Text库(即组件),那就需要添加RCTText的subspec。同理,Image需要加入RCTImage,等等。

Podfile

在 CocoaPods 的 Podfile 中指定我们所需要使用的组件。如果原生没有用过 CocoaPods 管理;

//在iOS原生代码所在的目录中(也就是.xcodeproj文件所在的目录)执行:
pod init

Podfile 会创建在执行命令的目录中。你需要调整其内容以满足你的植入需求。调整后的Podfile的内容看起来类似下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# target的名字一般与你的项目名字相同
target 'NumberTileGame' do

# 'node_modules'目录一般位于根目录中
# 但是如果你的结构不同,那你就要根据实际路径修改下面的`:path`
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'RCTText',
'RCTNetwork',
'RCTWebSocket', # 这个模块是用于调试功能的
# 在这里继续添加你所需要的模块
]

end

Pod安装

创建好了Podfile后,就可以开始安装React Native的pod包了。

pod install

4. 代码集成

原生代码集成

在要混合开发的页面, 加入如下代码

1
2
3
4
5
6
7
8
9
10
11
12
// RNViewController 页面
- (void)viewDidLoad {
[super viewDidLoad];

NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL : jsCodeLocation
moduleName : @"HybridApp"
initialProperties : @{@"page" : @"RN1"} // 传入参数,用于定义那个页面或者一些数据传输
launchOptions : nil];
self.view = rootView;
}

ReactNative代码集成

index.ios.js js 代码的入口

1
2
3
4
5
'use strict';
import {AppRegistry} from 'react-native';
import App from './app';
AppRegistry.registerComponent('HybridApp', () => App);
// 注册模块

app.js 在这边做页面判断,当然你也可以根据自己的需求去做调整

1
2
3
4
5
6
7
8
9
10
11
12
13
'use strict';

import React from 'react';
import RN1 from './ReactNative1';
import RN2 from './ReactNative2';

export default class App extends React.Component {
render() {
return (
this.props['page'] == 'RN1' ? <RN1 />:<RN2 />
);
}
}

ReactNative1.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
25
26
27
28
29
30
31
'use strict';

import React from 'react';
import {
StyleSheet,
Text,
View
} from 'react-native';
export default class ReactNative1 extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.highScoresTitle}>ReactNative1</Text>
<View style={{height: 100, width: 100, backgroundColor: 'blue'}} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FFFFFF',
},
highScoresTitle: {
fontSize: 20,
textAlign: 'center',
margin: 10,
}
});

ReactNative2.js 页面2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
'use strict';
import React from 'react';
import {
StyleSheet,
Text,
View
} from 'react-native';
export default class ReactNative1 extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.highScoresTitle}>ReactNative2</Text>
<View style={{height: 100, width: 100, backgroundColor: 'red'}} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FFFFFF',
},
highScoresTitle: {
fontSize: 20,
textAlign: 'center',
margin: 10,
}
});

>>项目gayhub地址<<
效果展示

因为微软开源的这个做的还不错,所以拿来用了

1. CodePush CLI安装

  • 首先要安装Node.js
  • CodePush CLI安装 : npm install -g code-push-cli

2. Account 账号管理

  • 注册code-push register
    codePush注册页面
    一般可以使用github登录(建议),也可以注册微软的账号!
  • 退出账号后可以用code-push link,进入页面
  • 登录 code-push login

    条状页面,通过上面的那些图登录
    选择登录方式

    登录的key

    输入key

  • 当前登录账号 code-push whoami
  • 退出当前登录 code-push logout
  • 显示在那些电脑上面登录过账号 code-push session ls
  • 移除在某台电脑的登录 code-push session rm <machineName>

  • 获取access-key不通过浏览器 code-push access-key add "VSTS Integration"
  • 通过上面的key登录 code-push login --accessKey <accessKey>
  • 设置key到期时间 code-push access-key patch <accessKeyName> --name "new name" --ttl 10d
  • 登录HTTPS_PROXY or HTTP_PROXY安全的一些东西code-push login --noProxy code-push login --proxy https://foo.com:3454

3. App 管理

  • 新建推送热更新的App code-push app add <appName>

    建议是iOS和安卓版本分开创建
    code-push app add MyApp-Android
    code-push app add MyApp-iOS

  • 对App进行改名字 code-push app rename <appName> <newAppName>

  • 移除App code-push app rm <appName>

  • 列出账号的所有App code-push app ls

  • App 增加参与的管理者(一般少用) code-push collaborator add <appName> <collaboratorEmail>

  • 移除参与的管理者 code-push collaborator rm <appName> <collaboratorEmail>

  • 列出所有的参与者 code-push collaborator ls <appName>

  • 把自己的这个App管理权限转移给其他人 code-push app transfer <appName> <newOwnerEmail>

4. 开发环境管理

  • 增加开发环境 code-push deployment add <appName> <deploymentName>
  • 移除 code-push deployment rm <appName> <deploymentName>
  • 换名字 code-push deployment rename <appName> <deploymentName> <newDeploymentName>
  • 列出所有的开发环境 code-push deployment ls <appName>
  • 列出所有的开发环境和对应access-key code-push deployment ls <appName> --displayKeys 或者 -k
    环境

    Active 当前表示激活比例
    Total 总共多少
    Pending 处于未升级和不确定因素
    Rollbacks 回滚的数
    Rollout
    Disabled

5. 更新版本管理

  • 最简单的方法 code-push release-react MyApp ios code-push release-react MyApp android
  • code-push release-react hxlivei ios -d “PrePush” –des “停车场”

    code-push release-react
    [–bundleName ]
    [–deploymentName ]
    [–description ]
    [–development ]
    [–disabled ]
    [–entryFile ]
    [–mandatory]
    [–plistFile ]
    [–plistFilePrefix ]
    [–sourcemapOutput ]
    [–targetBinaryVersion ]
    [–rollout ]

6. 其他

  • 清除更新记录 code-push deployment clear <appName> <deploymentName>
    清除历史

1.电脑安装CodePush环境

  1. 安装命令cnpm install -g code-push-cli

  2. 查看版本code-push -v

  3. 注册账号code-push register

  4. 登陆 code-push login

  5. 注销 code-push logout

  6. 列出 登陆的token code-push access-key ls

  7. 删除某个access-key code-push access-key rm <accessKey>

CodePush一些app管理指令

  1. 增加app的指令 code-push app add <appName>

  2. 列出所有app code-push app ls

  3. 更名 code-push app rename 旧名字 新名字

  4. 删除 code-push app rm 旧名字

CodePush app部署管理指令

默认部署类型 Production Staging,还可以自己加例如dev alpha beta等

  1. 列出app的部署环境 code-push deployment ls <appName>

  2. 增加部署环境 code-push deployment add <appName> <部署名字>

  3. 重命名部署名字:code-push deployment rename app名字 旧部署名字 新部署名字

  4. 删除部署名字 code-push deployment rm app名字 部署名字

  5. 查看历史版本code-push deployment history appName deploymentName

1.CodePush 项目目录下安装

npm install –save react-native-code-push@latest

2.iOS Setup

2.1推荐使用RNPM安装-React Native Package Manager

工具安装命令 npm i -g rnpm

Run rnpm link react-native-code-push

其他安装方法

react-native bundle –platform ios –entry-file index.ios.js –bundle-output main.jsbundle

code-push release newCPT react-native bundle –platform ios –entry-file index.ios.js –bundle-output main.jsbundl 1.0.0 [–deploymentName Staging] [–description 描述1.0.0] [–mandatory true]

React/React Native 的ES5 ES6写法对照表

###1. 模块引用

ES5 使用CommonJS标准,引入React包基本通过require进行

1
2
3
4
5
6
7
8
9
10
11
var React = require("react");
var {
Component,
PropTypes
} = React; //引用React抽象组件

var ReactNative = require("react-native");
var {
Image,
Text,
} = ReactNative; //引用具体的React Native组件

ES6 import写法更为标准

1
2
3
4
5
6
7
8
9
import React, { 
Component,
PropTypes,
} from 'react';
import {
Image,
Text
} from 'react-native';
//注意在React Native里,import直到0.12+才能正常运作。

###2. 导出单个类

ES5 一般通过 module.exports 来导出

1
2
3
4
5
6
var MyComponent = React.createClass({
...
});
module.exports = MyComponent;
//引用的时候也类似:
var MyComponent = require('./MyComponent');

ES6 通常用export default来实现相同的功能

1
2
3
4
5
export default class MyComponent extends Component{
...
}
//引用的时候也类似:
import MyComponent from './MyComponent';

###3. 定义组件

ES5 通过React.createClass来定义一个组件类

1
2
3
4
5
6
7
var Photo = React.createClass({
render: function() {
return (
<Image source={this.props.source} />
);
},
});

ES6 通过定义一个继承自React.Component的class来定义一个组件类

1
2
3
4
5
6
7
class Photo extends React.Component {
render() {
return (
<Image source={this.props.source} />
);
}
}

###4. 给组件定义方法

ES5 名字: function()

1
2
3
4
5
6
7
8
9
10
var Photo = React.createClass({
componentWillMount: function(){

},
render: function() {
return (
<Image source={this.props.source} />
);
},
});

ES6 名字()

1
2
3
4
5
6
7
8
9
10
class Photo extends React.Component {
componentWillMount() {

}
render() {
return (
<Image source={this.props.source} />
);
}
}

###5. 定义组件的属性类型和默认属性

ES5 属性类型和默认属性分别通过 propTypes 成员和 getDefaultProps 方法来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Video = React.createClass({
getDefaultProps: function() {
return {
autoPlay: false,
maxLoops: 10,
};
},
propTypes: {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
},
render: function() {
return (
<View />
);
},
});

ES6 可以统一使用static成员来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Video extends React.Component {
static defaultProps = {
autoPlay: false,
maxLoops: 10,
}; // 注意这里有分号
static propTypes = {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
}; // 注意这里有分号
render() {
return (
<View />
);
} // 注意这里既没有分号也没有逗号
}

###6. 初始化STATE

ES5 getInitialState: function() { return {key: value}; },

1
2
3
4
5
6
7
var Video = React.createClass({
getInitialState: function() {
return {
loopsRemaining: this.props.maxLoops,
};
},
})

ES6 两种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//写法1
class Video extends React.Component {
state = {
loopsRemaining: this.props.maxLoops,
}
}
//写法2 我们推荐更易理解的在构造函数中初始化(这样你还可以根据需要做一些计算)
class Video extends React.Component {
constructor(props){
super(props);
this.state = {
loopsRemaining: this.props.maxLoops,
};
}
}

###7. 把方法作为回调提供

ES5 在ES5下,React.createClass会把所有的方法都bind一遍,这样可以提交到任意的地方作为回调函数,而this不会变化。但官方现在逐步认为这反而是不标准、不易理解的。

1
2
3
4
5
6
7
8
9
10
11
12
13
var PostInfo = React.createClass({
handleOptionsButtonClick: function(e) {
// Here, 'this' refers to the component instance.
this.setState({showOptionsModal: true});
},
render: function(){
return (
<TouchableHighlight onPress={this.handleOptionsButtonClick}>
<Text>{this.props.label}</Text>
</TouchableHighlight>
)
},
});

ES6 在ES6下,你需要通过bind来绑定this引用,或者使用箭头函数(它会绑定当前scope的this引用)来调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class PostInfo extends React.Component
{
handleOptionsButtonClick(e){
this.setState({showOptionsModal: true});
}
render(){
return (
<TouchableHighlight
onPress={this.handleOptionsButtonClick.bind(this)}
onPress={e=>this.handleOptionsButtonClick(e)}
>
<Text>{this.props.label}</Text>
</TouchableHighlight>
)
},
}

箭头函数实际上是在这里定义了一个临时的函数,箭头函数的箭头=>之前是一个空括号、单个的参数名、或用括号括起的多个参数名,而箭头之后可以是一个表达式(作为函数的返回值),或者是用花括号括起的函数体(需要自行通过return来返回值,否则返回的是undefined)。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 箭头函数的例子
()=>1
v=>v+1
(a,b)=>a+b
()=>{
alert("foo");
}
e=>{
if (e == 0){
return 0;
}
return 1000/e;
}

需要注意的是,不论是bind还是箭头函数,每次被执行都返回的是一个新的函数引用,因此如果你还需要函数的引用去做一些别的事情(譬如卸载监听器),那么你必须自己保存这个引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 错误的做法
class PauseMenu extends React.Component{
componentWillMount(){
AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));
}
componentDidUnmount(){
AppStateIOS.removeEventListener('change', this.onAppPaused.bind(this));
}
onAppPaused(event){
}
}
// 正确的做法
class PauseMenu extends React.Component{
constructor(props){
super(props);
this._onAppPaused = this.onAppPaused.bind(this);
}
componentWillMount(){
AppStateIOS.addEventListener('change', this._onAppPaused);
}
componentDidUnmount(){
AppStateIOS.removeEventListener('change', this._onAppPaused);
}
onAppPaused(event){
}
}
从这个帖子中我们还学习到一种新的做法:
// 正确的做法
class PauseMenu extends React.Component{
componentWillMount(){
AppStateIOS.addEventListener('change', this.onAppPaused);
}
componentDidUnmount(){
AppStateIOS.removeEventListener('change', this.onAppPaused);
}
onAppPaused = (event) => {
//把方法直接作为一个arrow function的属性来定义,初始化的时候就绑定好了this指针
}
}

###8. ES6+带来的其它好处

解构&属性延展

结合使用ES6+的解构和属性延展,我们给孩子传递一批属性更为方便了。这个例子把className以外的所有属性传递给div标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class AutoloadingPostsGrid extends React.Component {
render() {
var {
className,
...others, // contains all properties of this.props except for className
} = this.props;
return (
<div className={className}>
<PostsGrid {...others} />
<button onClick={this.handleLoadMoreClick}>Load more</button>
</div>
);
}
}
//下面这种写法,则是传递所有属性的同时,用覆盖新的className值:
<div {...this.props} className="override">

</div>
//这个例子则相反,如果属性中没有包含className,则提供默认的值,而如果属性中已经包含了,则使用属性中的值
<div className="base" {...this.props}>

</div>

1. 特定平台扩展名

React Native会检测某个文件是否具有.ios.或是.android.的扩展名,然后根据当前运行的平台加载正确对应的文件。
假设你的项目中有如下两个文件:
BigButton.ios.js
BigButton.android.js
这样命名组件后你就可以在其他组件中直接引用,而无需关心当前运行的平台是哪个。
import BigButton from './components/BigButton';

2. 实用的方法是Platform.select()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var { Platform } = React;
var styles = StyleSheet.create({
container: {
flex: 1,
...Platform.select({
ios: {
backgroundColor: 'red',
},
android: {
backgroundColor: 'blue',
},
}),
},
});
//上面的代码会根据平台的不同返回不同的container样式——iOS上背景色为红色,而android为蓝色。
这一方法可以接受任何合法类型的参数,因此你也可以直接用它针对不同平台返回不同的组件,像下面这样:
var Component = Platform.select({
ios: () => require('ComponentIOS'),
android: () => require('ComponentAndroid'),
})();
<Component />;

3. 平台模块

React Native提供了一个检测当前运行平台的模块。如果组件只有一小部分代码需要依据平台定制,那么这个模块就可以派上用场。

1
2
3
4
5
import { Platform } from 'react-native';
var styles = StyleSheet.create({
height: (Platform.OS === 'ios') ? 200 : 100,
});
//Platform.OS在iOS上会返回ios,而在Android设备或模拟器上则会返回android。

4. 检测Android版本

1
2
3
4
5
//在Android上,平台模块还可以用来检测当前所运行的Android平台的版本:
import { Platform } from 'react-native';
if(Platform.Version === 21){
console.log('Running on Lollipop!');
}

摘自React Native CN

ReactNative style样式之Flexbox布局样式


Flexbox布局样式 ReactNative是参考文档大于0.29的版本


参考链接2

参考链接1

1. 6个属性设置在容器上

1.1 任何一个容器都可以指定为Flex布局。ReactNative无

display: flex;

display: inline-flex; //行内元素也可以使用Flex布局。
flex布局元素

1.2 flex-direction 属性决定主轴的方向(即项目的排列方向)。

flex-direction: row(默认值) | row-reverse | column | column-reverse;


ReactNative 中的 flexDirection: column(默认值) | column-reverse | row | row-reverse;
flex-direction 排列方向

1.3 flex-wrap 换行

flex-wrap: nowrap | wrap | wrap-reverse;


ReactNative 中的 flexWrap: nowrap | wrap ;
flex-wrap 换行

1.4 flex-flow flex-direction 和flex-wrap 的简写 ReactNative无

flex-flow : row || nowrap;
flex-flow

1.5 justify-content 水平排列方式

justify-content: flex-start | flex-end | center | space-between | space-around;
justify-content 水平排列方式


ReactNative中的 justifyContent: flex-start | flex-end | center | space-between | space-around;
justify-content 水平排列方式

1.6 align-items 垂直排列方式

align-items: flex-start | flex-end | center | baseline | stretch;
align-items 垂直排列方式


ReactNative中的 alignItems: flex-start | flex-end | center | stretch;
align-items 垂直排列方式

2. 容器中项目的属性

2.1 order ReactNative无

order :<number>; /* default 0 */属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
order

2.2 flex-grow ReactNative无

flex-grow:<number>; /* default 0 */属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
flex-grow

2.3 flex-shrink ReactNative无

flex-shrink: ; /* default 1 */属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。flex-basis
flex-shrink

2.4 flex-basis ReactNative无

flex-basis

2.5 flex ReactNative无

flex

2.6 align-self

align-self: auto | flex-start | flex-end | center | baseline | stretch;


ReactNative中的 alignSelf: auto | flex-start | flex-end | center | stretch;

3. FlexBox布局目前支持React Native 的属性

1
2
3
4
5
6
7
8
9
FlexBox布局目前支持React Native 的属性有如下6个:
(1) flex :number 大于0的时候才可伸缩,按照大小显示伸缩比例;
(2) flexDirection :column(容器设置的属性:主轴方向默认-垂直方向,从上倒下)| `column-reverse` | row(水平方向,从左到右)| `row-reverse`;
(3) flexWrap :wrap(换行) --nowrap(不换行)//容器设置的属性
(4) alignItems :flex-start | flex-end | center | stretch (容器设置的属性:伸缩项目在伸缩容器中的交叉轴的对齐方式);
(5) justifyContent : flex-start | flex-end | center | space-between | space-aronud (容器设置的属性:伸缩项目沿着垂直方向的对齐方式);
(6) alignSelf : flex-start | flex-end | center | auto | stretch。(伸缩项目在交叉轴的对齐方式);
(7) position : 'absolute' | 'relative' ;
(8) zIndex : number ;

组件的详细说明和生命周期(Component Specs and Lifecycle)


Mounting Cycle 挂载循环周期

  1. constructor(object props)
1
2
1. object getIniitialState() //它的返回值会成为this.state的初始值
2. object getDefaultProps() //它的返回值会成为this.props的初始值
  1. componentWillMount()//本地读取数据用于显示,进行读取的好时机
  2. render() -> React Element
  3. componentDidMount()//获取网络数据比较好的选择

Updating Cycle 组件更新循环周期

  1. componentWillReceiveProps(object nextProps)
  2. shouldComponentUpdate(object nextProps, object nextState) -> boolean
  3. componentWillUpdate(object nextProps, object nextState)
  4. render() -> React Element
  5. componentDidUpdate(object prevProps, object prevState)

1.挂载: componentWillMount

2.挂载: componentDidMount

3.更新: componentWillReceiveProps

componentWillReceiveProps(object nextProps)

4.更新: shouldComponentUpdate

boolean shouldComponentUpdate(object nextProps, object nextState)

5.更新: componentWillUpdate

componentWillUpdate(object nextProps, object nextState)

6.移除: componentWillUnmount


1.挂载: componentWillMount

1
componentWillMount()

服务器端和客户端都只调用一次,在初始化渲染执行之前立刻调用。如果在这个方法内调用 setState,render() 将会感知到更新后的 state,将会执行仅一次,尽管 state 改变了。

2.挂载: componentDidMount

1
componentDidMount()

在初始化渲染执行之后立刻调用一次,仅客户端有效(服务器端不会调用)。在生命周期中的这个时间点,组件拥有一个 DOM 展现,你可以通过 this.getDOMNode() 来获取相应 DOM 节点。
如果想和其它 JavaScript 框架集成,使用 setTimeout 或者 setInterval 来设置定时器,或者发送 AJAX 请求,可以在该方法中执行这些操作。

3.更新: componentWillReceiveProps

1
componentWillReceiveProps(object nextProps)

在组件接收到新的 props 的时候调用。在初始化渲染的时候,该方法不会调用。
用此函数可以作为 react 在 prop 传入之后, render() 渲染之前更新 state 的机会。老的 props 可以通过 this.props 获取到。在该函数中调用 this.setState() 将不会引起第二次渲染。

1
2
3
4
5
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}

4.更新: shouldComponentUpdate

1
boolean shouldComponentUpdate(object nextProps, object nextState)

在接收到新的 props 或者 state,将要渲染之前调用。该方法在初始化渲染的时候不会调用,在使用 forceUpdate 方法的时候也不会。
如果确定新的 props 和 state 不会导致组件更新,则此处应该 返回 false。

1
2
3
shouldComponentUpdate: function(nextProps, nextState) {
return nextProps.id !== this.props.id;
}

如果 shouldComponentUpdate 返回 false,则 render() 将不会执行,直到下一次 state 改变。(另外,componentWillUpdate 和 componentDidUpdate 也不会被调用。)
默认情况下,shouldComponentUpdate 总会返回 true,在 state 改变的时候避免细微的 bug,但是如果总是小心地把 state 当做不可变的,在 render() 中只从 props 和 state 读取值,此时你可以覆盖 shouldComponentUpdate 方法,实现新老 props 和 state 的比对逻辑。
如果性能是个瓶颈,尤其是有几十个甚至上百个组件的时候,使用 shouldComponentUpdate 可以提升应用的性能。

5.更新: componentWillUpdate

1
componentWillUpdate(object nextProps, object nextState)

在接收到新的 props 或者 state 之前立刻调用。在初始化渲染的时候该方法不会被调用。
使用该方法做一些更新之前的准备工作。

6.移除: componentWillUnmount

1
componentWillUnmount()

在组件从 DOM 中移除的时候立刻被调用。
在该方法中执行任何必要的清理,比如无效的定时器,或者清除在 componentDidMount 中创建的 DOM 元素。

Hexo 博客的安装流程

1. 配置环境

因为本人是用 Mac 所以就以主要介绍 Mac 的环境为主;

1.1 安装 Node 环境

作用:用来生成静态页面的

方法1. 直接上 https://nodejs.org/ 官网下载安装

方法2. Homebrew 安装
Homebrew 是 Mac 系统的包管理器,用于安装 NodeJS 和一些其他必需的工具软件。

首先, 安装 Homebrew;

1
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

复制上面代码在终端里面运行,等待安装完毕;

接下来在用 Homebrew 安装 Node;

1
brew install node

复制上面代码在终端里面运行,等待安装完毕;

1.2 Github 或者 Coding 账户都可以

作用:是用来做博客的远程创库、域名、服务器之类的,怎么与本地hexo建立连接等下讲。

2. 正式安装 Hexo

2.1 安装 Hexo 的命令行工具

1
npm install -g hexo

复制上面代码在终端里面运行,等待安装完毕;

2.2 初始化博客

1
hexo init 你的博客名

在终端中前往你要放置博客的文件夹位置, 运行上面代码,等待安装完毕;

2.3 配置 Github 仓库

登录到你自己的 github 账号, 点击 new repository 开始创建仓库, 按照下面的填写你自己要的参数;
创建仓库
配置 github pages 页面, 点击 Settings , 在 GitHub Pages 中开启配置为主分支
配置 github pages

2.4 配置 Hexo 文件

打开 Hexo 创建的根目录下的 _config.yml 文件,主要配置如下
_config.yml 文件

1
2
3
4
5
6
7
# Site
title: you blog // 你博客的名称标题
subtitle: // 你博客的小标题(选填)
description: 好好过一生 // 博客的描述,可以写短小的座右铭
author: FerryVip // 作者你的名字
language: zh-Hans // 设置博客的语音(选填,默认英文)
timezone: // 时区(选填)
1
2
3
4
5
# Deployment
deploy:
type: git // 类型就填 git
repo: git@github.com:strawferry/FerryBlog.git // 你的仓库地址,在配置 github 仓库成功后会有,可以用 HTTPS (需要输入密码登录)和 SSH(需要配置SSH)
branch: master // 默认主分支
1
npm install hexo-deployer-git --save

复制上面代码在终端的 hexo 目录下里面运行,等待安装完毕;

2.5 node 生成静态文件

hexo generate 或者 hexo g

复制上面代码在终端的 hexo 目录下里面运行,等待运行完毕;

2.6 本地运行查看

1
2
3
hexo server
或者
hexo s

复制上面代码在终端的 hexo 目录下里面运行,等待运行完毕, 显示通过访问 http://localhost:4000/ 就可以预览博客;

2.6 上传博客文件到 github

1
2
3
hexo deploy
或者
hexo d

复制上面代码在终端的 hexo 目录下里面运行,等待运行完毕;
等待运行完毕基本就能访问刚才在 github 配置 pages 的链接了;

2.7 编写你的博客

1
hexo new "new article"

之后在source/_posts目录下面,多了一个new-article.md的文件。
你通过编辑 new-article.md 就可以写你的日记了;

2.8 一些常用的命令

hexo clean // 清除文件

hexo generate // 生成静态文件

hexo deploy // 部署代码上去

hexo new "文章标题" // 创建新文章

3. 配置 Hexo 主题

官方默认主题你懂得,不怎么好看,果断自己换个,本人觉得 next 的主题还可以,这边就以配置这个主题举个栗子;
官方使用文档

主题下载

git clone https://github.com/iissnan/hexo-theme-next themes/next

复制上面代码在终端的 hexo 目录下里面运行,等待下载完毕;

主题更改

1
2
3
4
# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: landscape // 改为 next

到此基本配置 Hexo 和 主题更换已经都会了,要更好使用,还需要自己看下文档去详细配置;

说说之前配置遇到的一个坑, 前面我没有说到,就是要等大家都配置完了,大伙也会碰到的问题,在最后给大家说一下

就是在本地跑 hexo server 一切看起来都好好的,样式都没啥问题, 当去看 pages 的时候,发现样式惨不忍睹,这就对了,因为还有一个东西还没有配置;

1
2
3
4
5
6
# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: http://yoursite.com // 把你在 pages 配置的那个网址链接复制在这个地方就解决了
root: /
permalink: :year/:month/:day/:title/
permalink_defaults: