ReactNative開發的一些經驗
本文主要是本人從Android原生到開發出一套完整的RN模組嵌入原生的經驗。
state與props
他們二者是RN的資料核心。state主要是自身資料更新,props主要是父元件傳入到子元件中使用,也會有自身的預設props。 對於props,建議一般都傳入子元件,防止以後進行修改。特定的flag則進行指定,比如:有一個Page A,他包含Component B
A extends Component{
...
render(){
return (
...
<B url=this.state.url {...this.state}/>
...
);
}
...
}
複製程式碼
對於State,需要注意死迴圈,這種情況可能發生在render過程(不是事件響應過程),假如在渲染過程中,我們需要修改state,一般先進行判斷,然後再去設定,否則可能一直死迴圈render比如下面的ImageHolderComponent例子。
Component
在RN中,提倡元件化。他是可以根據UI劃分元件,對於底層元件,我們需要實現邏輯不需要實現業務。比如,對於styles,我們一般需要提供一個預設樣式,然後再通過外部插入的props樣式,通過陣列組合一起,外部可以覆蓋內部。 例如,下面例子是定義一個通用的Image元件,他可以在圖片載入失敗或者是URL為空等適合顯示出place holder圖片。
export default class ImageHolderComponent extends Component {
static defaultProps = {
url: "",
imageStyle: {},
holderResizeMode: "cover"
};
constructor(props) {
super(props);
this.state = {
loadState: 0
}
}
_emptyUri = () => {
return <Image
resizeMode={this.props.holderResizeMode}
source={this._holder()}
style={[{
width: 60,
height: 60,
borderRadius: 60,
}, this.props.imageStyle]}/>;
};
_normal = () => {
return <Image
source={{uri: this.props.url}}
defaultSource={this._holder()}
onError={() => {
if (this.state.loadState === 1) {
//防止死迴圈
return;
}
this.setState({loadState: 1});
}}
onLoad={() => {
if (this.state.loadState === 2) {
////防止死迴圈
return
}
this.setState({loadState: 2});
}
}
style={[{
width: 60,
height: 60,
borderRadius: 60,
}, this.props.imageStyle]}/>;
};
_holder = () => {
const {holder} = this.props;
if (holder) {
return holder();
}
return require("../../img/pic_empty_data.png");
};
render() {
const showView = this.props.url && (this.props.url.indexOf("http") >= 0) && this.state.loadState !== 1 ? this._normal() : this._emptyUri();
return (
<TouchableWithoutFeedback onPress={() => {
const {onPress} = this.props;
if (onPress) {
onPress(this.props.url);
}
}}>
{showView}
</TouchableWithoutFeedback>
);
}
}
複製程式碼
他需要外部傳入一個url即可,假如需要複寫樣式則傳入引數名為imageStyle的style即可。 這樣子,就做到Component獨立,又能與外部進行互動。
fetch封裝
因為fetch是返回一個Promise的,所以還是比較友好的。 我們可以對fetch進行一個比較好的封裝,需求是:
- 外部需要傳入引數即可
- 返回一個Promise
- 對於基本錯誤,我們能夠catch返回。
function _body(method, params) {
const _params = JSON.stringify(params);
console.log(("http=> params = " + _params));
return {method: method, headers: HEADER, body: _params}
}
function _get(url, params) {
return _request(url, _body("GET", params));
}
function _post(url, params) {
return _request(url, _body("POST", params))
}
//發起請求
function _request(url, params, timeout) {
console.log(("http=> url = " + url));
const request = fetch(url, params)
.then(result => {
if (result.ok) {
console.log(("http=> respond = " + result.json()));
return result.json();
} else {
return Promise.reject({code: -1, message: "請求失敗"});
}
})
.then(value => {
//可以進行進一步刷選
return value;
})
.catch(error => {
return Promise.reject(error);
});
return _wrapRequest(request, timeout)
}
//設定超時,預設10000
function _wrapRequest(promise, timeout = 10000) {
return Promise.race([promise, new Promise(function (resolve, reject) {
setTimeout(() => {
reject({code: -1, message: "請求超時"});
},
timeout);
})]).catch(e => {
return Promise.reject(e)
})
}
複製程式碼
比如對於登陸而言
//登陸
function _body(password,username) {
const params = {};
params[USER_NAME] = username;
params[PASSWORD] = password;
....
return _post("loginurl",params)
}
複製程式碼
就可以了。
啟用程式碼檢測
個人認為這個還是比較有必要的,這樣子可以統一程式碼風格,什麼分號,括號,無用匯入,一些可能出現錯誤的地方,通過ESLint都可以檢測出來。需要可以前往ReactNativeDemo 檢視.eslint檔案和package.json檔案。
分包+程式碼
在開發中,一般獨立頁面使用Page結尾,一般元件使用Component結尾。 對於對內方法新增下劃線,對外方法就不用,還是使用駝峰的命名規則。 一般,我們儘量使用解構賦值取資料,這樣子可以給一些預設值。
方法問題
在RN中,他相對於CSS頂層的this是window,RN頂層的this是本身Component,所以我們儘量使用方法變數
而不是定義方法,這樣子他就可以自動繫結this了。例如
A extends Component{
//不推薦
methodA(){
//...
}
//推薦
methodB = ()=>{
//。。
}
}
複製程式碼
navigate
我們進行頁面跳轉的時候,比如使用的是react-navigation
,我們儘量帶一個from
這樣子一個key,value是該頁面的String值。這樣子方便下一個頁面無論是goback還是navigate去別的頁面在過把存款都比較好操作
Android物理back鍵
對於Android物理back鍵,我們可能在特殊的頁面需要進行特殊處理,比如back的時候需要彈窗什麼的。 但是,在RN中,比如一個需求是A.navigate(B),然後B.navigate(C)。假如是有這樣子一個跳轉關係,在B中需要監聽back鍵,這時候在C中沒有監聽,在C中按下back鍵就會把B也結束掉,因為B中的監聽並沒有被移除,而在C中,他是沒有處理back event的返回值的,導致B響應了,就連續返回了。 需要處理該問題,我們就需要定義一個BaseComponent,以後每一個獨立頁面Page,都需要繼承該Component,假如是有特殊處理的,需要注意super。
function log(msg) {
console.log("BaseComponentPage==>" + msg);
}
export default class BaseComponentPage extends Component {
constructor(props) {
super(props);
log("addEventListener hardwareBackPress");
BackHandler.addEventListener("hardwareBackPress", this._onBackPress);
}
//子類需要重寫該方法到時候,必須super
componentWillUnmount() {
log("removeEventListener hardwareBackPress");
BackHandler.removeEventListener("hardwareBackPress", this._onBackPress);
}
_onBackPress = () => {
this.props.navigation.goBack();
log("_onBackPress");
return true;
};
}
複製程式碼
常用第三方開源Package
react-navigation
導航react-native-router-flux
導航,他更加友好的封裝了react-navigation
react-redux
,redux
,redux-logger
,redux-thunk
redux全家桶react-native-scrollable-tab-view
可以做到類似Android的TabLayoutprop-types
型別檢測react-native-video
視訊播放react-native-image-zoom-viewer
圖片縮放react-native-fetch-blob
網路請求,也可以自己直接封裝一下fetch。react-native-cached-image
圖片快取+顯示