React Native 01:学习笔记

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

DraggedImage.png
  • package.json

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

DraggedImage-2.png
上面那串等于从index.ios.js文件中创建了一个名字叫AwesomeProject的view,并且添加到window的rootVC的view上。
  • 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.定义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>
           );
       }
Simulator Screen Shot 2016年10月27日 上午11.42.51.png
  • 列(高度比例: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>
           );
       }
Simulator Screen Shot 2016年10月27日 上午11.35.11.png
PS:利用这个就可以完成一些复杂的网格布局。
    例如这种多层嵌套的布局:
1902568823-56fde33a311f0_articlex-1.png

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>
DraggedImage-4.png
  • 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>
DraggedImage-5-1.png
  • 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>
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对象。使用起来更加方便。

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()不生效。


    .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]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容