React Native 01:學(xué)習(xí)筆記

Learn once, write anywhere.

React Native是Facebook弄出來的一款用來開發(fā)真正原生、可渲染iOS和 Android移動(dòng)應(yīng)用的JavaScrit框架。它其實(shí)就是基于Javascript和React的基礎(chǔ)上來開發(fā)原生應(yīng)用。并且一份代碼可以同時(shí)支持iOS和Android,也就是能夠做到真正意義上的跨平臺(tái)。React Native和React的區(qū)別在于,React是將瀏覽器作為渲染平臺(tái)的,RN則是將移動(dòng)設(shè)備作為渲染平臺(tái)。代碼上的體現(xiàn)就是用web思想去寫原生的代碼。所以RN具有Native的一個(gè)最大的優(yōu)勢(shì),它可以做到熱更新,也就是不需要發(fā)包就可以改點(diǎn)東西。


IDE

Nuclide:Nuclide 是 Facebook 推出的一套基于 Atom 的開發(fā)工具集。提供自動(dòng)完成和JavaScript類型檢查,內(nèi)建React開發(fā)支持,并支持 Facebook 最新的 React Native 庫,支持 Facebook 的 Flow JavaScript 類型檢查器。
PS:推薦使用Atom配合Nuclide來搞RN開發(fā)。反正是好用。


HelloWorld

DraggedImage.png
  • package.json

DraggedImage-1.png
這個(gè)文件主要是用來配置一些信息的,添加一些依賴庫的。然后需要利用npm執(zhí)行npm install命令來安裝模塊到node_modules目錄。
PS:npm是一個(gè)Node的模塊管理器。我們只需要一行命令就能將一些開源的模塊給搞下來。感覺和cocoapods功能有點(diǎn)像。_
  • iOS工程

DraggedImage-2.png
上面那串等于從index.ios.js文件中創(chuàng)建了一個(gè)名字叫AwesomeProject的view,并且添加到window的rootVC的view上。
  • index.ios.js

DraggedImage-3.png

ES5&ES6

不管是ES5還是ES6都只是JS的一個(gè)規(guī)范,RN中并沒有強(qiáng)制規(guī)定你要用哪個(gè)。但是RN官方還有很多大神們都建議我們直接入手ES6。但是問題就來了,你從開源網(wǎng)站上clone下來的代碼就有的人用ES5,有的人用ES6了。所有知道這兩個(gè)的區(qū)別是有必要的。不然你就會(huì)看到如下類似的紅色頁面:

5478D0A5F1183C3D2EDEB67CCCBB36DE.jpg.png

語法

  • Class
    JS中的Class具有很多面向?qū)ο笳Z言的特性,雖然它也有屬性,方法。還有繼承等。但都只是假象。它并不是真正意義上的面向?qū)ο笳Z言中的那種class。它其實(shí)通過構(gòu)造函數(shù)的形式。將類的屬性和方法都定義在構(gòu)造函數(shù)的prototype對(duì)象上面。額。好吧。用的時(shí)候還是像面向?qū)ο蟮哪欠N方式使用。
//1.定義class
class Point { 
  constructor(x, y) {// 構(gòu)造函數(shù)
     this.x = x;
     this.y = y;
   } 
  toString() { 
    return '(' + this.x + ', ' + this.y + ')'; 
   }
}
// 2.創(chuàng)建實(shí)例
var a = new Point();
a.toString();

上面定義了一個(gè)Point類,有2個(gè)屬性(x和y),一個(gè)構(gòu)造方法(constructor)還有一個(gè)方法(toString)。
一個(gè)class對(duì)應(yīng)一個(gè)構(gòu)造函數(shù)。當(dāng)你使用new創(chuàng)建一個(gè)該類的實(shí)例的時(shí)候,它會(huì)調(diào)用該構(gòu)造函數(shù)。同樣一個(gè)class可以生成個(gè)多個(gè)的實(shí)例對(duì)象,但是所有的實(shí)例對(duì)象都共有這一個(gè)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,并且在構(gòu)造函數(shù)里利用suepr()方法調(diào)用了父類的構(gòu)造函數(shù)。
JS中的繼承,子類可以具有父類所有屬性和方法。但是在子類的構(gòu)造函數(shù)里,必須要調(diào)用super(),這樣才能在子類中使用this關(guān)鍵字。

  • 回調(diào)函數(shù)和 Promise
    在RN中經(jīng)常要用到異步編程。所以就需要將執(zhí)行的結(jié)果回調(diào)出去。一般采用2種方式。一種是回調(diào)函數(shù),另一種是Promise。
    例如:
1.回調(diào)函數(shù)
 //定義
 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);
    });

  • 關(guān)于引用
    ES5
//1.導(dǎo)入
 var React = require("react");
 var {
       Component, 
       PropTypes
 } = React;
 var ReactNative = require("react-native");
 var {
       Image,
       Text,
 } = ReactNative; 
 //2.導(dǎo)出
 var MyComponent = React.createClass({ 
      ...
 });
 module.exports = MyComponent;
 //3.引用
 var MyComponent = require('./MyComponent');

ES6

//1.導(dǎo)入
 import React, { 
      Component, 
      PropTypes,
 } from 'react';
 import { 
      Image, 
      Text,
 } from 'react-native'
 //2.導(dǎo)出
 export default class MyComponent extends Component{ 
      ...
 }
 //3.引用
 import MyComponent from './MyComponent';
  • 關(guān)于組件
    ES5
//1.組件
 var Photo = React.createClass({ 
        render: function() {
               return ( 
                  <Image source={this.props.source} /> 
              ); 
        },
 });
 //2.組件方法
 test: function(){ 
 },
 //3.Props
 getDefaultProps: function() { //默認(rèn)屬性
      return { 
            imageId: 0, 
      }; 
 },
 propTypes: {  //屬性類型
      imageId: React.PropTypes.number.isRequired, 
 },
 //4.state
 getInitialState: function() { 
      return { 
          iconName:'', 
      }; 
 },
 //5.bind()
 在ES5下,React.createClass會(huì)把所有的方法都bind一遍,
 例如:給按鈕綁定點(diǎn)擊方法的時(shí)候不需要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 = { //默認(rèn)屬性
    imageId: 0, 
 }; 
 static propTypes = { //屬性類型
     imageId: React.PropTypes.number.isRequired,
 }; 
 //4.State
 constructor(props){
      super(props); 
      this.state = { 
              iconName: '', 
      };
  }
 //5.bind()
 在ES6下,你需要通過bind來綁定this引用,或者使用箭頭函數(shù)(它會(huì)綁定當(dāng)前scope的this引用)來調(diào)用
 例如:
 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:利用這個(gè)就可以完成一些復(fù)雜的網(wǎng)格布局。
    例如這種多層嵌套的布局:
1902568823-56fde33a311f0_articlex-1.png

5.圖片(resizeMode)

  • contain(模式容器完全容納圖片,圖片寬高自適應(yīng))
 <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(圖片會(huì)被截取并鋪滿容器)
 <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(圖片拉伸適應(yīng)模式容器)
<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

組件的生命周期

關(guān)于RN中組件的生命周期很類似于iOS中VC中的View的生命周期。一開始在調(diào)試的時(shí)候會(huì)發(fā)現(xiàn)組件的render方法調(diào)用的非常頻繁。所以知道組件的生命周期是很有必要。這樣我們可以在適當(dāng)?shù)姆椒ɡ锩嫱瓿上鄳?yīng)的事情,比如在componentDidMount添加通知,componentWillUnmount中移除通知等等。

DraggedImage-7.png

getDefaultProps:組件實(shí)例創(chuàng)建前調(diào)用,多個(gè)實(shí)例間共享引用。注意:如果父組件傳遞過來的Props和你在該函數(shù)中定義的Props的key一樣,將會(huì)被覆蓋。
getInitalState:組件示例創(chuàng)建的時(shí)候調(diào)用的第一個(gè)函數(shù)。主要用于初始化state。注意:為了在使用中不出現(xiàn)空值,建議初始化state的時(shí)候盡可能給每一個(gè)可能用到的值都賦一個(gè)初始值。
componentWillMount:在render前,getInitalState之后調(diào)用。僅調(diào)用一次,可以用于改變state操作。
render:組件渲染函數(shù),會(huì)返回一個(gè)Virtual DOM,只允許返回一個(gè)最外層容器組件。render函數(shù)盡量保持純凈,只渲染組件,不修改狀態(tài),不執(zhí)行副操作(比如計(jì)時(shí)器)。
componentDidMount:在render渲染之后,React會(huì)根據(jù)Virtual DOM來生成真實(shí)DOM,生成完畢后會(huì)調(diào)用該函數(shù)。在瀏覽器端(React),我們可以通過this.getDOMNode()來拿到相應(yīng)的DOM節(jié)點(diǎn)。然而我們?cè)赗N中并用不到,在RN中主要在該函數(shù)中執(zhí)行網(wǎng)絡(luò)請(qǐng)求,定時(shí)器開啟等相關(guān)操作
** componentWillReceiveProps(nextProps) **:props改變(父容器來更改或是redux),將會(huì)調(diào)用該函數(shù)。新的props將會(huì)作為參數(shù)傳遞進(jìn)來,老的props可以根據(jù)this.props來獲取。我們可以在該函數(shù)中對(duì)state作一些處理。注意:在該函數(shù)中更新state不會(huì)引起二次渲染。
** boolean shouldComponentUpdate(object nextProps, object nextState) **:該函數(shù)傳遞過來兩個(gè)參數(shù),新的state和新的props。state和props的改變都會(huì)調(diào)到該函數(shù)。該函數(shù)主要對(duì)傳遞過來的nextProps和nextState作判斷。如果返回true則重新渲染,如果返回false則不重新渲染。在某些特定條件下,我們可以根據(jù)傳遞過來的props和state來選擇更新或者不更新,從而提高效率。
** componentWillUpdate(object nextProps, object nextState) **:與componentWillMount方法類似,組件上會(huì)接收到新的props或者state渲染之前,調(diào)用該方法。但是不可以在該方法中更新state和props。
** componentDidUpdate(object prevProps,object prevState) **:和初始化時(shí)期的componentDidMount類似,在render之后,真實(shí)DOM生成之后調(diào)用該函數(shù)。傳遞過來的是當(dāng)前的props和state。在該函數(shù)中同樣可以使用this.getDOMNode()來拿到相應(yīng)的DOM節(jié)點(diǎn)。如果你需要在運(yùn)行中執(zhí)行某些副操作,請(qǐng)?jiān)谠摵瘮?shù)中完成。
componentWillUnmount:組件DOM中移除的時(shí)候調(diào)用。在這里進(jìn)行一些相關(guān)的銷毀操作,比如定時(shí)器,監(jiān)聽等等。


React Native

存儲(chǔ)

RN官方有封裝一個(gè)AsyncStorage組件,采用key-value的形式用來處理一些數(shù)據(jù)存儲(chǔ)操作。
PS:更推薦使用react-native-storage這個(gè)開源組件,它是對(duì)AsyncStorage的一層封裝,并且他每個(gè)方法都是會(huì)返回一個(gè)Promise對(duì)象。使用起來更加方便。

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({
   // 最大容量,默認(rèn)值1000條數(shù)據(jù)循環(huán)存儲(chǔ)
   size: 1000,
 
   // 存儲(chǔ)引擎:對(duì)于RN使用AsyncStorage,對(duì)于web使用window.localStorage
  // 如果不指定則數(shù)據(jù)只會(huì)保存在內(nèi)存中,重啟后即丟失
   storageBackend: AsyncStorage,
 
   // 數(shù)據(jù)過期時(shí)間,默認(rèn)一整天(1000 * 3600 * 24 毫秒),設(shè)為null則永不過期
   defaultExpires: null,
 
   // 讀寫時(shí)在內(nèi)存中緩存數(shù)據(jù)。默認(rèn)啟用。
   enableCache: true,
 
  // 如果storage中沒有相應(yīng)數(shù)據(jù),或數(shù)據(jù)已過期,
   // 則會(huì)調(diào)用相應(yīng)的sync同步方法,無縫返回最新數(shù)據(jù)。
   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來保存數(shù)據(jù)。這些數(shù)據(jù)一般是全局獨(dú)有的,常常需要調(diào)用的。
  // 除非你手動(dòng)移除,這些數(shù)據(jù)會(huì)被永久保存,而且默認(rèn)不會(huì)過期。
  storage.save({
    key: key,  //注意:請(qǐng)不要在key中使用_下劃線符號(hào)!
    rawData: data,
     // 如果不指定過期時(shí)間,則會(huì)使用defaultExpires參數(shù)
     // 如果設(shè)為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);
     //如果找到數(shù)據(jù),則在then方法中返回
   }).catch(err => {
     //如果沒有找到數(shù)據(jù)且沒有同步方法,
    //或者有其他異常,則在catch中返回
     errorCallback(err);
   })  
 },
 
 module.exports = StorageUtil;

網(wǎng)絡(luò)

RN的網(wǎng)絡(luò)組件封裝的非常好用。直接上代碼吧。

 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宏括起來的方法都是異步執(zhí)行的,如果方法里涉及到UI的操作,需要放到主線程里執(zhí)行。RN中的橋接方式比JSBridge好用多了。


React Native踩坑匯總

  • 1.RN中沒有VC的概念。它把view的生命周期事件封裝在了view里面,而不是像iOS中用VC去管理view的生命周期。suoyi RN中的轉(zhuǎn)場(chǎng)都只是view層的切換。
  • 2.this.setState()不生效。


    .png

    react中的setState 是異步執(zhí)行的,修改狀態(tài)后并沒有馬上生效, setState 函數(shù)接受兩個(gè)參數(shù),一個(gè)是一個(gè)對(duì)象,就是設(shè)置的狀態(tài),還有一個(gè)是一個(gè)回調(diào)函數(shù),就是設(shè)置狀態(tài)成功之后執(zhí)行的。所以正確做法。


    屏幕快照 2016-11-08 下午5.23.52.png
  • 3.RN中ImageView加載網(wǎng)絡(luò)圖片,只做了內(nèi)存緩存,而沒有做磁盤緩存。


    讀取緩存 .png

    添加緩存.png

    上面代碼中_decodedImageCache對(duì)象的類型是NSCache??梢钥闯鯮N并沒有做磁盤緩存。

  • 4.RN的網(wǎng)絡(luò)請(qǐng)求沒有被NSURLProtocol攔截。
    在想怎么給RN中的圖片做磁盤緩存的時(shí)候,調(diào)試的過程中發(fā)現(xiàn)RN中通過fetch發(fā)起的所有請(qǐng)求都沒有被注冊(cè)的NSURLProtocol攔截,那么問題就來了。沒有被攔截,難道是RN的網(wǎng)絡(luò)用了更底層的東西?

  • 5.關(guān)于動(dòng)態(tài)下發(fā)代碼,有兩種做法。

    • 在程序一啟動(dòng)的時(shí)候,判斷是否需要更新,然后去下載所有代碼打包后zip包。之后需要顯示RN的view的時(shí)候就可以去加載對(duì)應(yīng)的view就可以了。
    • 一個(gè)頁面對(duì)應(yīng)一個(gè)url,通過這個(gè)url去請(qǐng)求單獨(dú)頁面的RN代碼。這樣可以做到每次進(jìn)入的時(shí)候都會(huì)加載最新的頁面。也就是像瀏覽器那樣重新打開,重新去請(qǐng)求頁面。

    但是最后還是選擇了第一種方式,同時(shí)保留了第二種。在封裝的vc上,提供自動(dòng)刷新的屬性。也支持每次進(jìn)入頁面都自動(dòng)更新代碼。

附錄:
學(xué)習(xí)資料
[http://reactnative.cn/]
[https://github.com/reactnativecn/react-native-guide]
[http://m.itdecent.cn/p/7c43af022758]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容