美文网首页技术文章
React Native 01:学习笔记

React Native 01:学习笔记

作者: Null先森的内存地址 | 来源:发表于2016-11-08 17:35 被阅读529次

    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]

    相关文章

      网友评论

      本文标题:React Native 01:学习笔记

      本文链接:https://www.haomeiwen.com/subject/eognuttx.html