Learn once, write anywhere.
React Native是Facebook弄出來的一款用來開發(fā)真正原生有决、可渲染iOS和 Android移動應用的JavaScrit框架介衔。它其實就是基于Javascript和React的基礎(chǔ)上來開發(fā)原生應用。并且一份代碼可以同時支持iOS和Android烁登,也就是能夠做到真正意義上的跨平臺艳悔。React Native和React的區(qū)別在于,React是將瀏覽器作為渲染平臺的,RN則是將移動設備作為渲染平臺喳瓣。代碼上的體現(xiàn)就是用web思想去寫原生的代碼。所以RN具有Native的一個最大的優(yōu)勢赞别,它可以做到熱更新畏陕,也就是不需要發(fā)包就可以改點東西。
IDE
Nuclide:Nuclide 是 Facebook 推出的一套基于 Atom 的開發(fā)工具集仿滔。提供自動完成和JavaScript類型檢查惠毁,內(nèi)建React開發(fā)支持,并支持 Facebook 最新的 React Native 庫崎页,支持 Facebook 的 Flow JavaScript 類型檢查器鞠绰。
PS:推薦使用Atom配合Nuclide來搞RN開發(fā)。反正是好用飒焦。
HelloWorld
-
package.json
這個文件主要是用來配置一些信息的蜈膨,添加一些依賴庫的。然后需要利用npm執(zhí)行npm install命令來安裝模塊到node_modules目錄牺荠。
PS:npm是一個Node的模塊管理器翁巍。我們只需要一行命令就能將一些開源的模塊給搞下來。感覺和cocoapods功能有點像休雌。_
-
iOS工程
上面那串等于從index.ios.js文件中創(chuàng)建了一個名字叫AwesomeProject的view灶壶,并且添加到window的rootVC的view上。
-
index.ios.js
ES5&ES6
不管是ES5還是ES6都只是JS的一個規(guī)范杈曲,RN中并沒有強制規(guī)定你要用哪個驰凛。但是RN官方還有很多大神們都建議我們直接入手ES6。但是問題就來了担扑,你從開源網(wǎng)站上clone下來的代碼就有的人用ES5,有的人用ES6了恰响。所有知道這兩個的區(qū)別是有必要的。不然你就會看到如下類似的紅色頁面:
語法
- Class
JS中的Class具有很多面向?qū)ο笳Z言的特性魁亦,雖然它也有屬性,方法羔挡。還有繼承等洁奈。但都只是假象间唉。它并不是真正意義上的面向?qū)ο笳Z言中的那種class。它其實通過構(gòu)造函數(shù)的形式利术。將類的屬性和方法都定義在構(gòu)造函數(shù)的prototype對象上面呈野。額。好吧印叁。用的時候還是像面向?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)建實例
var a = new Point();
a.toString();
上面定義了一個Point類,有2個屬性(x和y)轮蜕,一個構(gòu)造方法(constructor)還有一個方法(toString)昨悼。
一個class對應一個構(gòu)造函數(shù)。當你使用new創(chuàng)建一個該類的實例的時候跃洛,它會調(diào)用該構(gòu)造函數(shù)率触。同樣一個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葱蝗,并且在構(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.導入
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';
- 關(guān)于組件
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引用,或者使用箭頭函數(shù)(它會綁定當前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>
);
}
- 列(高度比例: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:利用這個就可以完成一些復雜的網(wǎng)格布局档冬。
例如這種多層嵌套的布局:
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>
組件的生命周期
關(guān)于RN中組件的生命周期很類似于iOS中VC中的View的生命周期。一開始在調(diào)試的時候會發(fā)現(xiàn)組件的render方法調(diào)用的非常頻繁酷誓。所以知道組件的生命周期是很有必要披坏。這樣我們可以在適當?shù)姆椒ɡ锩嫱瓿上鄳氖虑椋热缭赾omponentDidMount添加通知盐数,componentWillUnmount中移除通知等等棒拂。
getDefaultProps:組件實例創(chuàng)建前調(diào)用,多個實例間共享引用。注意:如果父組件傳遞過來的Props和你在該函數(shù)中定義的Props的key一樣帚屉,將會被覆蓋谜诫。
getInitalState:組件示例創(chuàng)建的時候調(diào)用的第一個函數(shù)。主要用于初始化state攻旦。注意:為了在使用中不出現(xiàn)空值喻旷,建議初始化state的時候盡可能給每一個可能用到的值都賦一個初始值。
componentWillMount:在render前牢屋,getInitalState之后調(diào)用且预。僅調(diào)用一次,可以用于改變state操作烙无。
render:組件渲染函數(shù)锋谐,會返回一個Virtual DOM,只允許返回一個最外層容器組件皱炉。render函數(shù)盡量保持純凈怀估,只渲染組件,不修改狀態(tài)合搅,不執(zhí)行副操作(比如計時器)多搀。
componentDidMount:在render渲染之后,React會根據(jù)Virtual DOM來生成真實DOM灾部,生成完畢后會調(diào)用該函數(shù)康铭。在瀏覽器端(React),我們可以通過this.getDOMNode()來拿到相應的DOM節(jié)點赌髓。然而我們在RN中并用不到从藤,在RN中主要在該函數(shù)中執(zhí)行網(wǎng)絡請求,定時器開啟等相關(guān)操作
** componentWillReceiveProps(nextProps) **:props改變(父容器來更改或是redux)锁蠕,將會調(diào)用該函數(shù)夷野。新的props將會作為參數(shù)傳遞進來,老的props可以根據(jù)this.props來獲取荣倾。我們可以在該函數(shù)中對state作一些處理悯搔。注意:在該函數(shù)中更新state不會引起二次渲染。
** boolean shouldComponentUpdate(object nextProps, object nextState) **:該函數(shù)傳遞過來兩個參數(shù)舌仍,新的state和新的props妒貌。state和props的改變都會調(diào)到該函數(shù)。該函數(shù)主要對傳遞過來的nextProps和nextState作判斷铸豁。如果返回true則重新渲染灌曙,如果返回false則不重新渲染。在某些特定條件下节芥,我們可以根據(jù)傳遞過來的props和state來選擇更新或者不更新在刺,從而提高效率。
** componentWillUpdate(object nextProps, object nextState) **:與componentWillMount方法類似,組件上會接收到新的props或者state渲染之前蚣驼,調(diào)用該方法忍燥。但是不可以在該方法中更新state和props。
** componentDidUpdate(object prevProps,object prevState) **:和初始化時期的componentDidMount類似隙姿,在render之后,真實DOM生成之后調(diào)用該函數(shù)厂捞。傳遞過來的是當前的props和state输玷。在該函數(shù)中同樣可以使用this.getDOMNode()來拿到相應的DOM節(jié)點。如果你需要在運行中執(zhí)行某些副操作靡馁,請在該函數(shù)中完成欲鹏。
componentWillUnmount:組件DOM中移除的時候調(diào)用。在這里進行一些相關(guān)的銷毀操作臭墨,比如定時器赔嚎,監(jiān)聽等等。
React Native
存儲
RN官方有封裝一個AsyncStorage組件胧弛,采用key-value的形式用來處理一些數(shù)據(jù)存儲操作尤误。
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條數(shù)據(jù)循環(huán)存儲
size: 1000,
// 存儲引擎:對于RN使用AsyncStorage,對于web使用window.localStorage
// 如果不指定則數(shù)據(jù)只會保存在內(nèi)存中茵宪,重啟后即丟失
storageBackend: AsyncStorage,
// 數(shù)據(jù)過期時間最冰,默認一整天(1000 * 3600 * 24 毫秒),設為null則永不過期
defaultExpires: null,
// 讀寫時在內(nèi)存中緩存數(shù)據(jù)稀火。默認啟用暖哨。
enableCache: true,
// 如果storage中沒有相應數(shù)據(jù),或數(shù)據(jù)已過期憾股,
// 則會調(diào)用相應的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ù)一般是全局獨有的茴恰,常常需要調(diào)用的。
// 除非你手動移除斩熊,這些數(shù)據(jù)會被永久保存往枣,而且默認不會過期。
storage.save({
key: key, //注意:請不要在key中使用_下劃線符號!
rawData: data,
// 如果不指定過期時間,則會使用defaultExpires參數(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)絡
RN的網(wǎng)絡組件封裝的非常好用集乔。直接上代碼吧。
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)場都只是view層的切換哩罪。
-
2.this.setState()不生效。
.png
react中的setState 是異步執(zhí)行的巡验,修改狀態(tài)后并沒有馬上生效际插, setState 函數(shù)接受兩個參數(shù),一個是一個對象显设,就是設置的狀態(tài)腹鹉,還有一個是一個回調(diào)函數(shù),就是設置狀態(tài)成功之后執(zhí)行的敷硅。所以正確做法功咒。
屏幕快照 2016-11-08 下午5.23.52.png
-
3.RN中ImageView加載網(wǎng)絡圖片,只做了內(nèi)存緩存绞蹦,而沒有做磁盤緩存力奋。
讀取緩存 .png
添加緩存.png
上面代碼中_decodedImageCache對象的類型是NSCache∮钠撸可以看出RN并沒有做磁盤緩存景殷。
4.RN的網(wǎng)絡請求沒有被NSURLProtocol攔截。
在想怎么給RN中的圖片做磁盤緩存的時候澡屡,調(diào)試的過程中發(fā)現(xiàn)RN中通過fetch發(fā)起的所有請求都沒有被注冊的NSURLProtocol攔截猿挚,那么問題就來了。沒有被攔截驶鹉,難道是RN的網(wǎng)絡用了更底層的東西绩蜻?-
5.關(guān)于動態(tài)下發(fā)代碼,有兩種做法室埋。
- 在程序一啟動的時候办绝,判斷是否需要更新伊约,然后去下載所有代碼打包后zip包。之后需要顯示RN的view的時候就可以去加載對應的view就可以了孕蝉。
- 一個頁面對應一個url屡律,通過這個url去請求單獨頁面的RN代碼。這樣可以做到每次進入的時候都會加載最新的頁面降淮。也就是像瀏覽器那樣重新打開超埋,重新去請求頁面。
但是最后還是選擇了第一種方式佳鳖,同時保留了第二種纳本。在封裝的vc上,提供自動刷新的屬性腋颠。也支持每次進入頁面都自動更新代碼。
附錄:
學習資料
[http://reactnative.cn/]
[https://github.com/reactnativecn/react-native-guide]
[http://www.reibang.com/p/7c43af022758]