Learn once, write anywhere.
React Native是Facebook弄出来的一款用来开发真正原生、可渲染iOS和 Android移动应用的JavaScrit框架。它其实就是基于Javascript和React的基础上来开发原生应用。并且一份代码可以同时支持iOS和Android,也就是能够做到真正意义上的跨平台。React Native和React的区别在于,React是将浏览器作为渲染平台的,RN则是将移动设备作为渲染平台。代码上的体现就是用web思想去写原生的代码。所以RN具有Native的一个最大的优势,它可以做到热更新,也就是不需要发包就可以改点东西。
IDE
Nuclide:Nuclide 是 Facebook 推出的一套基于 Atom 的开发工具集。提供自动完成和JavaScript类型检查,内建React开发支持,并支持 Facebook 最新的 React Native 库,支持 Facebook 的 Flow JavaScript 类型检查器。
PS:推荐使用Atom配合Nuclide来搞RN开发。反正是好用。
HelloWorld
-
package.json
这个文件主要是用来配置一些信息的,添加一些依赖库的。然后需要利用npm执行npm install命令来安装模块到node_modules目录。
PS:npm是一个Node的模块管理器。我们只需要一行命令就能将一些开源的模块给搞下来。感觉和cocoapods功能有点像。_
-
iOS工程
上面那串等于从index.ios.js文件中创建了一个名字叫AwesomeProject的view,并且添加到window的rootVC的view上。
-
index.ios.js
ES5&ES6
不管是ES5还是ES6都只是JS的一个规范,RN中并没有强制规定你要用哪个。但是RN官方还有很多大神们都建议我们直接入手ES6。但是问题就来了,你从开源网站上clone下来的代码就有的人用ES5,有的人用ES6了。所有知道这两个的区别是有必要的。不然你就会看到如下类似的红色页面:
语法
- Class
JS中的Class具有很多面向对象语言的特性,虽然它也有属性,方法。还有继承等。但都只是假象。它并不是真正意义上的面向对象语言中的那种class。它其实通过构造函数的形式。将类的属性和方法都定义在构造函数的prototype对象上面。额。好吧。用的时候还是像面向对象的那种方式使用。
//1.定义class
class Point {
constructor(x, y) {// 构造函数
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
// 2.创建实例
var a = new Point();
a.toString();
上面定义了一个Point类,有2个属性(x和y),一个构造方法(constructor)还有一个方法(toString)。
一个class对应一个构造函数。当你使用new创建一个该类的实例的时候,它会调用该构造函数。同样一个class可以生成个多个的实例对象,但是所有的实例对象都共有这一个class原型。
- 继承(extends)
class Point3D extends Point {
constructor(x, y, z) {
super(x,y);
this.z = z;
}
toString() {
return '(' + this.x + ', ' + this.y + ',' + this.z + ')';
}
}
上面Point3D类继承与Point,并且在构造函数里利用suepr()方法调用了父类的构造函数。
JS中的继承,子类可以具有父类所有属性和方法。但是在子类的构造函数里,必须要调用super(),这样才能在子类中使用this关键字。
- 回调函数和 Promise
在RN中经常要用到异步编程。所以就需要将执行的结果回调出去。一般采用2种方式。一种是回调函数,另一种是Promise。
例如:
1.回调函数
//定义
StorageUtil.load = function(key,successCallback,errorCallback){
storage.load({
key: key,
}).then(ret => {
successCallback(ret);
}).catch(err => {
errorCallback(err);
})
}
//使用
StorageUtil.load(
APPID_KEY,
(data)=> {
console.log('data'+data);
},
(err)=> {
console.log('err'+err);
}
);
2.Promise
//定义
StorageUtil.load2 = function(key){
return new Promise((resolve, reject) =>{
storage.load({
key: key,
}).then(ret => {
resolve(ret);
}).catch(err => {
reject(err);
})
});
}
//使用
StorageUtil.load2(APPID_KEY).then((data)=>{
console.log('[load2]---'+data);
})
.catch((error)=>{
console.log('err'+err);
});
坑
- 关于引用
ES5
//1.导入
var React = require("react");
var {
Component,
PropTypes
} = React;
var ReactNative = require("react-native");
var {
Image,
Text,
} = ReactNative;
//2.导出
var MyComponent = React.createClass({
...
});
module.exports = MyComponent;
//3.引用
var MyComponent = require('./MyComponent');
ES6
//1.导入
import React, {
Component,
PropTypes,
} from 'react';
import {
Image,
Text,
} from 'react-native'
//2.导出
export default class MyComponent extends Component{
...
}
//3.引用
import MyComponent from './MyComponent';
- 关于组件
ES5
//1.组件
var Photo = React.createClass({
render: function() {
return (
<Image source={this.props.source} />
);
},
});
//2.组件方法
test: function(){
},
//3.Props
getDefaultProps: function() { //默认属性
return {
imageId: 0,
};
},
propTypes: { //属性类型
imageId: React.PropTypes.number.isRequired,
},
//4.state
getInitialState: function() {
return {
iconName:'',
};
},
//5.bind()
在ES5下,React.createClass会把所有的方法都bind一遍,
例如:给按钮绑定点击方法的时候不需要bind(this)
render: function(){
return (
<TouchableHighlight onPress={this.onClick}>
</TouchableHighlight>
)
},
ES6
//1.定义组件
class Photo extends React.Component {
render() {
return (
<Image source={this.props.source} />
);
}
}
//2.组件方法
test(){
},
//3.Props
static defaultProps = { //默认属性
imageId: 0,
};
static propTypes = { //属性类型
imageId: React.PropTypes.number.isRequired,
};
//4.State
constructor(props){
super(props);
this.state = {
iconName: '',
};
}
//5.bind()
在ES6下,你需要通过bind来绑定this引用,或者使用箭头函数(它会绑定当前scope的this引用)来调用
例如:
render(){
return (
<TouchableHighlight onPress={this.onClick.bind(this)} >
</TouchableHighlight>
<TouchableHighlight onPress={()=>this.onClick()}>
</TouchableHighlight>
)
},
React
felx布局
1.水平居中(alignItems:’center’)
2.垂直居中(justifyContent:’center’)
3.水平垂直居中(alignItems:’center’, justifyContent:’center’)
4.flexDirection(row, column)
栗如:
- 行(宽度比例1:2:1)
render() {
return (
<View style = {{flex:1,flexDirection:'row'}}>
<View style = {{flex:1,backgroundColor:'red'}}>
</View>
<View style = {{flex:2,backgroundColor:'blue'}}>
</View>
<View style = {{flex:1,backgroundColor:'yellow'}}>
</View>
</View>
);
}
- 列(高度比例:1:2:1)
render() {
return (
<View style = {{flex:1,flexDirection:'column'}}>
<View style = {{flex:1,backgroundColor:'red'}}>
</View>
<View style = {{flex:2,backgroundColor:'blue'}}>
</View>
<View style = {{flex:1,backgroundColor:'yellow'}}>
</View>
</View>
);
}
PS:利用这个就可以完成一些复杂的网格布局。
例如这种多层嵌套的布局:
5.图片(resizeMode)
- contain(模式容器完全容纳图片,图片宽高自适应)
<Text style={styles.welcome}> 100px height with resizeMode contain </Text>
<View style={[{flex: 1, backgroundColor: '#fe0000'}]}>
<Image style={{flex: 1, height: 100, resizeMode: Image.resizeMode.contain}} source={{uri: 'http://gtms03.alicdn.com/tps/i3/TB1Kcs5GXXXXXbMXVXXutsrNFXX-608-370.png'}} />
</View>
- cover(图片会被截取并铺满容器)
<Text style={styles.welcome}> 100px height with resizeMode cover </Text>
<View style={[{flex: 1, backgroundColor: '#fe0000'}]}>
<Image style={{flex: 1, height: 100, resizeMode: Image.resizeMode.cover}} source={{uri: 'http://gtms03.alicdn.com/tps/i3/TB1Kcs5GXXXXXbMXVXXutsrNFXX-608-370.png'}} />
</View>
- stretch(图片拉伸适应模式容器)
<Text style={styles.welcome}> 100px height with resizeMode stretch </Text>
<View style={[{flex: 1, backgroundColor: '#fe0000'}]}>
<Image style={{flex: 1, height: 100, resizeMode: Image.resizeMode.stretch}} source={{uri: 'http://gtms03.alicdn.com/tps/i3/TB1Kcs5GXXXXXbMXVXXutsrNFXX-608-370.png'}} />
</View>
组件的生命周期
关于RN中组件的生命周期很类似于iOS中VC中的View的生命周期。一开始在调试的时候会发现组件的render方法调用的非常频繁。所以知道组件的生命周期是很有必要。这样我们可以在适当的方法里面完成相应的事情,比如在componentDidMount添加通知,componentWillUnmount中移除通知等等。
getDefaultProps:组件实例创建前调用,多个实例间共享引用。注意:如果父组件传递过来的Props和你在该函数中定义的Props的key一样,将会被覆盖。
getInitalState:组件示例创建的时候调用的第一个函数。主要用于初始化state。注意:为了在使用中不出现空值,建议初始化state的时候尽可能给每一个可能用到的值都赋一个初始值。
componentWillMount:在render前,getInitalState之后调用。仅调用一次,可以用于改变state操作。
render:组件渲染函数,会返回一个Virtual DOM,只允许返回一个最外层容器组件。render函数尽量保持纯净,只渲染组件,不修改状态,不执行副操作(比如计时器)。
componentDidMount:在render渲染之后,React会根据Virtual DOM来生成真实DOM,生成完毕后会调用该函数。在浏览器端(React),我们可以通过this.getDOMNode()来拿到相应的DOM节点。然而我们在RN中并用不到,在RN中主要在该函数中执行网络请求,定时器开启等相关操作
** componentWillReceiveProps(nextProps) **:props改变(父容器来更改或是redux),将会调用该函数。新的props将会作为参数传递进来,老的props可以根据this.props来获取。我们可以在该函数中对state作一些处理。注意:在该函数中更新state不会引起二次渲染。
** boolean shouldComponentUpdate(object nextProps, object nextState) **:该函数传递过来两个参数,新的state和新的props。state和props的改变都会调到该函数。该函数主要对传递过来的nextProps和nextState作判断。如果返回true则重新渲染,如果返回false则不重新渲染。在某些特定条件下,我们可以根据传递过来的props和state来选择更新或者不更新,从而提高效率。
** componentWillUpdate(object nextProps, object nextState) **:与componentWillMount方法类似,组件上会接收到新的props或者state渲染之前,调用该方法。但是不可以在该方法中更新state和props。
** componentDidUpdate(object prevProps,object prevState) **:和初始化时期的componentDidMount类似,在render之后,真实DOM生成之后调用该函数。传递过来的是当前的props和state。在该函数中同样可以使用this.getDOMNode()来拿到相应的DOM节点。如果你需要在运行中执行某些副操作,请在该函数中完成。
componentWillUnmount:组件DOM中移除的时候调用。在这里进行一些相关的销毁操作,比如定时器,监听等等。
React Native
存储
RN官方有封装一个AsyncStorage组件,采用key-value的形式用来处理一些数据存储操作。
PS:更推荐使用react-native-storage这个开源组件,它是对AsyncStorage的一层封装,并且他每个方法都是会返回一个Promise对象。使用起来更加方便。
import React, { Component } from 'react';
import {
AsyncStorage,
} from 'react-native';
import Storage from 'react-native-storage';
global.USER = {
admin_id: '',
user_name: '',
admin_name: '',
expiry: 0,
auth_token: ''
};
global.APPID = 0;
global.USER_KEY = 'USERKEY';
global.APPID_KEY = 'APPIDKEY';
global.SHOW_ERROR = '0'; //0表示显示全部,1显示异常
var StorageUtil = {};
var storage = new Storage({
// 最大容量,默认值1000条数据循环存储
size: 1000,
// 存储引擎:对于RN使用AsyncStorage,对于web使用window.localStorage
// 如果不指定则数据只会保存在内存中,重启后即丢失
storageBackend: AsyncStorage,
// 数据过期时间,默认一整天(1000 * 3600 * 24 毫秒),设为null则永不过期
defaultExpires: null,
// 读写时在内存中缓存数据。默认启用。
enableCache: true,
// 如果storage中没有相应数据,或数据已过期,
// 则会调用相应的sync同步方法,无缝返回最新数据。
sync: {
}
});
StorageUtil.init = function(callback){
storage.getBatchData([
{ key: USER_KEY },
{ key: APPID_KEY }
]).then(results => {
console.log('[results]--'+ results);
USER = results[0];
APPID = results[1];
console.log('[init:USER]--'+ USER);
console.log('[init:APPID]--'+ APPID);
callback(true);
}).catch(err => {
console.log(err);
callback(false);
});
},
StorageUtil.save = function(key,data){
// 使用key来保存数据。这些数据一般是全局独有的,常常需要调用的。
// 除非你手动移除,这些数据会被永久保存,而且默认不会过期。
storage.save({
key: key, //注意:请不要在key中使用_下划线符号!
rawData: data,
// 如果不指定过期时间,则会使用defaultExpires参数
// 如果设为null,则永不过期
// expires: 1000 * 36000
});
if (key == USER_KEY) {
USER = data;
}else if (key == APPID_KEY) {
APPID = data;
}
console.log('[SAVE:USER]--'+ USER.user_name);
console.log('[SAVE:APPID]--'+ APPID);
},
StorageUtil.load = function(key,successCallback,errorCallback){
// 读取
storage.load({
key: key,
}).then(ret => {
successCallback(ret);
//如果找到数据,则在then方法中返回
}).catch(err => {
//如果没有找到数据且没有同步方法,
//或者有其他异常,则在catch中返回
errorCallback(err);
})
},
module.exports = StorageUtil;
网络
RN的网络组件封装的非常好用。直接上代码吧。
getMoviesFromApiAsync() {
return fetch('http://facebook.github.io/react-native/movies.json')
.then((response) => response.json())
.then((responseJson) => {
return responseJson.movies;
})
.catch((error) => {
console.error(error);
});
}
桥接
例如:
OC代码
RCT_EXPORT_METHOD(getVersion:(RCTResponseSenderBlock)callback)
{
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
callback(@[version]);
}
JS代码
ASHUtilManager.getVersion((version)=> {
this.setState({
versionText : 'V' + version,
});
});
PS:在RCT_EXPORT_METHOD宏括起来的方法都是异步执行的,如果方法里涉及到UI的操作,需要放到主线程里执行。RN中的桥接方式比JSBridge好用多了。
React Native踩坑汇总
- 1.RN中没有VC的概念。它把view的生命周期事件封装在了view里面,而不是像iOS中用VC去管理view的生命周期。suoyi RN中的转场都只是view层的切换。
-
2.this.setState()不生效。
react中的setState 是异步执行的,修改状态后并没有马上生效, setState 函数接受两个参数,一个是一个对象,就是设置的状态,还有一个是一个回调函数,就是设置状态成功之后执行的。所以正确做法。
-
3.RN中ImageView加载网络图片,只做了内存缓存,而没有做磁盘缓存。
上面代码中_decodedImageCache对象的类型是NSCache。可以看出RN并没有做磁盘缓存。
4.RN的网络请求没有被NSURLProtocol拦截。
在想怎么给RN中的图片做磁盘缓存的时候,调试的过程中发现RN中通过fetch发起的所有请求都没有被注册的NSURLProtocol拦截,那么问题就来了。没有被拦截,难道是RN的网络用了更底层的东西?-
5.关于动态下发代码,有两种做法。
- 在程序一启动的时候,判断是否需要更新,然后去下载所有代码打包后zip包。之后需要显示RN的view的时候就可以去加载对应的view就可以了。
- 一个页面对应一个url,通过这个url去请求单独页面的RN代码。这样可以做到每次进入的时候都会加载最新的页面。也就是像浏览器那样重新打开,重新去请求页面。
但是最后还是选择了第一种方式,同时保留了第二种。在封装的vc上,提供自动刷新的属性。也支持每次进入页面都自动更新代码。
附录:
学习资料
[http://reactnative.cn/]
[https://github.com/reactnativecn/react-native-guide]
[http://www.jianshu.com/p/7c43af022758]