React Native 01:学习笔记

React Native是Facebook弄出来的一款用来开发真正原生、可渲染iOS和 Android移动应用的JavaScrit框架。它其实就是基于Javascript和React的基础上来开发原生应用。并且一份代码可以同时支持iOS和Android,也就是能够做到真正意义上的跨平台。React Native和React的区别在于,React是将浏览器作为渲染平台的,RN则是将移动设备作为渲染平台。代码上的体现就是用web思想去写原生的代码。所以ReactNative具有Native的一个最大的优势,它可以做到热更新,也就是不需要发包就可以改点东西

IDE

Nuclide:Nuclide 是 Facebook 推出的一套基于 Atom 的开发工具集。提供自动完成和JavaScript类型检查,内建React开发支持,并支持 Facebook 最新的 React Native 库,支持 Facebook 的 Flow JavaScript 类型检查器。
PS:推荐使用Atom配合Nuclide来搞RN开发。反正是好用。

HelloWorld

DraggedImage.png

  • ####package.json

    这个文件主要是用来配置一些信息的,添加一些依赖库的。然后需要利用npm执行npm install命令来安装模块到node_modules目录。
    PS:npm是一个Node的模块管理器。我们只需要一行命令就能将一些开源的模块给搞下来。感觉和cocoapods功能有点像。

DraggedImage-1.png

  • ####iOS工程

    从index.ios.js文件中创建了一个名字叫AwesomeProject的view,并且添加到window的rootVC的view上。
    DraggedImage-2.png

  • ####index.ios.js

    DraggedImage-3.png

ES5&ES6

不管是ES5还是ES6都只是JS的一个规范,RN中并没有强制规定你要用哪个。但是RN官方还有很多大神们都建议我们直接入手ES6。但是问题就来了,你从开源网站上clone下来的代码就有的人用ES5,有的人用ES6了。所有知道这两个的区别是有必要的。不然你就会看到如下类似的红色页面:

5478D0A5F1183C3D2EDEB67CCCBB36DE.jpg.png

类(Class )

JS中的Class具有很多面向对象语言的特性,虽然它也有属性,方法。还有继承等。但都只是假象。它并不是真正意义上的面向对象语言中的那种class。它其实通过构造函数的形式。将类的属性和方法都定义在构造函数的prototype对象上面。额。好吧。用的时候还是像面向对象的那种方式使用。

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

1
2
3
4
5
6
7
8
9
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
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
40
41
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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //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
    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
    //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
    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
    //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

###flex布局

  • 1.水平居中(alignItems:’center’)

  • 2.垂直居中(justifyContent:’center’)

  • 3.水平垂直居中(alignItems:’center’, justifyContent:’center’)

  • 4.flexDirection(row, column)

    栗如:

    • 行(宽度比例1:2:1)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      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>
      );
      }

      Simulator Screen Shot 2016年10月27日 上午11.42.51.png

    • 列(高度比例:1:2:1)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      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>
      );
      }

      Simulator Screen Shot 2016年10月27日 上午11.35.11.png

      PS:利用这个就可以完成一些复杂的网格布局,例如多层嵌套的布局。,

      1902568823-56fde33a311f0_articlex-1.png

  • 5.图片(resizeMode)

    • contain(模式容器完全容纳图片,图片宽高自适应)

      1
      2
      3
      4
      <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>

      DraggedImage-4.png

    • cover(图片会被截取并铺满容器)

      1
      2
      3
      4
      <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>

      DraggedImage-5-1.png

    • stretch(图片拉伸适应模式容器)

      1
      2
      3
      4
      <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>

      DraggedImage-6.png

###生命周期

关于RN中组件的生命周期很类似于iOS中VC中的View的生命周期。一开始在调试的时候会发现组件的render方法调用的非常频繁。所以知道组件的生命周期是很有必要。这样我们可以在适当的方法里面完成相应的事情,比如在componentDidMount添加通知,componentWillUnmount中移除通知等等。

DraggedImage-7.png

  • 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对象。使用起来更加方便。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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的网络组件封装的非常好用。直接上代码吧。

1
2
3
4
5
6
7
8
9
10
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);
});
}

###桥接
例如:

1
2
3
4
5
6
OC代码
RCT_EXPORT_METHOD(getVersion:(RCTResponseSenderBlock)callback)
{
  NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
  callback(@[version]);
}

1
2
3
4
5
6
JS代码
ASHUtilManager.getVersion((version)=> {
this.setState({
versionText : 'V' + version,
});
});

PS:在RCT_EXPORT_METHOD宏括起来的方法都是异步执行的,如果方法里涉及到UI的操作,需要放到主线程里执行。RN中的桥接方式比JSBridge好用多了。


##ReactNative疑问整理:

  • 1.RN中没有VC的概念。它把view的生命周期事件封装在了view里面,而不是像iOS中用VC去管理view的生命周期。suoyi RN中的转场都只是view层的切换。
  • 2.this.setState()不生效。
    .png
    react中的setState 是异步执行的,修改状态后并没有马上生效, setState 函数接受两个参数,一个是一个对象,就是设置的状态,还有一个是一个回调函数,就是设置状态成功之后执行的。所以正确做法。
    屏幕快照 2016-11-08 下午5.23.52.png
  • 3.RN中ImageView加载网络图片,只做了内存缓存,而没有做磁盘缓存。
    读取缓存 .png
    添加缓存.png
    上面代码中_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]

Rui,<br>.....。