前言
- 本文有配套視頻,可以酌情觀看。
- 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯(lián)系我。
- 文中所有內容僅供學習交流之用,不可用于商業(yè)用途,如因此引起的相關法律法規(guī)責任,與我無關。
- 如文中內容對您造成不便,煩請聯(lián)系 277511806@qq.com 處理,謝謝。
- 轉載麻煩注明出處,謝謝。
本篇資源:鏈接: https://pan.baidu.com/s/1i4HFmeT 密碼: yy79
源碼托管到 github 上,需要源碼的 點我下載,喜歡的話記得 Star,謝謝!
屬性聲明和屬性確定
有朋友反饋這邊 屬性聲明和屬性確定 不了解,這邊就來補充一下。
在 React-Native 創(chuàng)建的自定義組件是可以復用的,而開發(fā)過程中一個組件可能會由多個人同時開發(fā)或者多個人使用一個組件,為了讓開發(fā)人員之間減少溝通成本,我們會對某些必要的屬性進行屬性聲明,讓使用的人知道需要傳入什么!甚至有些需要傳入但沒有傳入值的屬性我們會進行警告處理!
這邊先來看下 屬性聲明 的示例:
static propTypes = {
name:PropTypes.string,
ID:PropTypes.number.isRequired,
}
上面我們聲明了
name
和ID
兩個屬性,并且進行了屬性的確認,其中,'isRequired' 表示如果不傳遞這個屬性,那么開發(fā)階段中,系統(tǒng)會出現(xiàn)警告,讓我們對其進行屬性確認,也就是說是否為必須屬性。-
屬性確認語法分為:
- 屬性為任何類型
React.PropTypes.any
- 屬性是否是 JavaScript 基本類型
React.PropTypes.array; React.PropTypes.func; React.PropTypes.bool; React.PropTypes.number; React.PropTypes.object; React.PropTypes.string;
- 屬性是某個 React 元素
React.PropTypes.element;
- 屬性為幾個特定的值
React.PropTypes.oneOf(['value1', 'value2'])
- 屬性為指定類型中的一個
React.PropTypes.oneOfType([ React.PropTypes.node, React.PropTypes.number, React.PropTypes.string ])
- 屬性為可渲染的節(jié)點
React.PropTypes.node;
- 屬性為某個指定類的實例
React.PropTypes.instanceOf(NameOfClass);
- 屬性為指定類型的數組
React.PropTypes.arrayOf(React.PropTypes.string)
- 屬性有一個指定的成員對象
React.PropTypes..objectOf(React.PropTypes.number)
- 屬性是一個指定構成方式的對象
React.PropTypes.shape({ color:React.PropTypes.stirng, fontSize:React.PropTypes.number })
- 屬性默認值(當我們沒有傳遞屬性的時候使用)
static defaultProps = { name:'蒼井空' };
占位圖
開發(fā)中,我們會有許多圖片都是從網絡進行請求的,但是,如果出現(xiàn)網絡卡頓的情況,圖片就會遲遲不出現(xiàn),又或者有的并沒有圖片,這樣圖片就為空白狀態(tài);為了不讓用戶感覺太突兀影響用戶體驗,也為了視圖整體性,一般我們會選擇使用占位圖先展示給用戶看,等到圖片加載完畢再將圖片展示出來。
這邊我們需要對cell內部進行一些處理。
{/* 左邊圖片 */}
<Image source={{uri:this.props.image === '' ? 'defaullt_thumb_83x83' : this.props.image}} style={styles.imageStyle} />
無數據情況處理
還是網絡問題,在網絡出現(xiàn)問題或者無法加載數據的時候,一般我們會展示空白頁,在空白頁中提示
無數據
之類的提示,比較好的還會使用指示器
的方式告訴用戶網絡出現(xiàn)問題等等。這邊我們做以下處理,當無數據時,我們就先初始化基礎界面,然后展示
提示
頁面,等到有數據時,再重新渲染數據。首先設置 無數據 頁面
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
} from 'react-native';
export default class GDNoDataView extends Component {
render() {
return(
<View style={styles.container}>
<Text style={styles.textStyle}>無數據 </Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex:1,
justifyContent:'center',
alignItems:'center',
},
textStyle: {
fontSize:21,
color:'gray'
}
});
- 接著,沒有數據的時候我們進行一些處理就可以了
// 根據網絡狀態(tài)決定是否渲染 listview
renderListView() {
if (this.state.loaded === false) {
return(
<NoDataView />
);
}else {
return(
<PullList
onPullRelease={(resolve) => this.fetchData(resolve)}
dataSource={this.state.dataSource}
renderRow={this.renderRow}
showsHorizontalScrollIndicator={false}
style={styles.listViewStyle}
initialListSize={5}
/>
);
}
}
listView 頭部設置
- 根據原版效果發(fā)現(xiàn) 提示標題 應該放到 ListView 的頭部才對,所以這邊就做下小修改。
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
showsHorizontalScrollIndicator={false}
style={styles.listViewStyle}
initialListSize={5}
renderHeader={this.renderHeader}
/>
- renderHeader 方法實現(xiàn)
// 返回 listview 頭部
renderHeader() {
return (
<View style={styles.headerPromptStyle}>
<Text>根據每條折扣的點擊進行統(tǒng)計,每5分鐘更新一次</Text>
</View>
);
}
下拉刷新
- 為了避免適配問題帶來的麻煩,這邊我們采用第三方框架
react-native-pull
實現(xiàn)下拉刷新和上拉加載更多的功能。
<PullList
onPullRelease={(resolve) => this.fetchData(resolve)}
dataSource={this.state.dataSource}
renderRow={this.renderRow}
showsHorizontalScrollIndicator={false}
style={styles.listViewStyle}
initialListSize={5}
renderHeader={this.renderHeader}
/>
- fetchData 方法修改
// 網絡請求
fetchData(resolve) {
setTimeout(() => {
fetch('http://guangdiu.com/api/gethots.php')
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.data),
loaded:true,
});
if (resolve !== undefined){
setTimeout(() => {
resolve(); // 關閉動畫
}, 1000);
}
})
.done();
});
}
網絡請求之POST(重要)
GET 和 POST 是我們請求 HTTP 接口常用的方式,針對表單提交的請求,我們通常采用 POST 的方式。
在 JQuery 中,傳入對象框架會自動封裝成 formData 的形式,但是在 fetch 中沒有這個功能,所以我們需要自己初始化一個 FormData 直接傳給 body (補充:FormData也可以傳遞字節(jié)流實現(xiàn)上傳圖片功能)。
let formData = new FormData();
formData.append("參數", "值");
formData.append("參數", "值");
fetch(url, {
method:'POST,
headers:{},
body:formData,
}).then((response)=>{
if (response.ok) {
return response.json();
}
}).then((json)=>{
alert(JSON.stringify(json));
}).catch.((error)=>{
console.error(error);
})
- 如果想詳細了解 Fetch ,可以點擊 React Native 之 網絡請求 查看。
首頁模塊
- 這邊我們按照前面提到的步驟,進行數據的加載。
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Image,
ListView,
Dimensions
} from 'react-native';
// 第三方
import {PullList} from 'react-native-pull';
const {width, height} = Dimensions.get('window');
// 引用外部文件
import CommunalNavBar from '../main/GDCommunalNavBar';
import CommunalHotCell from '../main/GDCommunalHotCell';
import HalfHourHot from './GDHalfHourHot';
import Search from './GDSearch';
export default class GDHome extends Component {
// 構造
constructor(props) {
super(props);
// 初始狀態(tài)
this.state = {
dataSource: new ListView.DataSource({rowHasChanged:(r1, r2) => r1 !== r2}),
loaded:true,
};
this.fetchData = this.fetchData.bind(this);
}
// 網絡請求
fetchData(resolve) {
let formData = new FormData();
formData.append("count", "30");
setTimeout(() => {
fetch('http://guangdiu.com/api/getlist.php', {
method:'POST',
headers:{},
body:formData,
})
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.data),
loaded:true,
});
if (resolve !== undefined){
setTimeout(() => {
resolve();
}, 1000);
}
})
.done();
});
}
// 跳轉到近半小時熱門
pushToHalfHourHot() {
this.props.navigator.push({
component: HalfHourHot,
})
}
// 跳轉到搜索
pushToSearch() {
this.props.navigator.push({
component:Search,
})
}
// 返回左邊按鈕
renderLeftItem() {
return(
<TouchableOpacity
onPress={() => {this.pushToHalfHourHot()}}
>
<Image source={{uri:'hot_icon_20x20'}} style={styles.navbarLeftItemStyle} />
</TouchableOpacity>
);
}
// 返回中間按鈕
renderTitleItem() {
return(
<TouchableOpacity>
<Image source={{uri:'navtitle_home_down_66x20'}} style={styles.navbarTitleItemStyle} />
</TouchableOpacity>
);
}
// 返回右邊按鈕
renderRightItem() {
return(
<TouchableOpacity
onPress={()=>{this.pushToSearch()}}
>
<Image source={{uri:'search_icon_20x20'}} style={styles.navbarRightItemStyle} />
</TouchableOpacity>
);
}
// 根據網絡狀態(tài)決定是否渲染 listview
renderListView() {
if (this.state.loaded === false) {
return(
<NoDataView />
);
}else {
return(
<PullList
onPullRelease={(resolve) => this.fetchData(resolve)}
dataSource={this.state.dataSource}
renderRow={this.renderRow}
showsHorizontalScrollIndicator={false}
style={styles.listViewStyle}
initialListSize={5}
renderHeader={this.renderHeader}
/>
);
}
}
// 返回每一行cell的樣式
renderRow(rowData) {
return(
<CommunalHotCell
image={rowData.image}
title={rowData.title}
/>
);
}
componentDidMount() {
this.fetchData();
}
render() {
return (
<View style={styles.container}>
{/* 導航欄樣式 */}
<CommunalNavBar
leftItem = {() => this.renderLeftItem()}
titleItem = {() => this.renderTitleItem()}
rightItem = {() => this.renderRightItem()}
/>
{/* 根據網絡狀態(tài)決定是否渲染 listview */}
{this.renderListView()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: 'white',
},
navbarLeftItemStyle: {
width:20,
height:20,
marginLeft:15,
},
navbarTitleItemStyle: {
width:66,
height:20,
},
navbarRightItemStyle: {
width:20,
height:20,
marginRight:15,
},
listViewStyle: {
width:width,
},
});
- OK,這邊也已經成功拿到數據,所以接著就是完成 cell 樣式部分就可以了。
效果:
navigator 跳轉動畫
有時候我們需要在跳轉的時候使用不同的跳轉動畫,比如我們 半小時熱門 的跳轉方式在
iOS
內叫 模態(tài)跳轉,特性就是當頁面退出后會直接銷毀,多用于注冊、登錄等不需要常駐內存的界面。react-native 中為了方便實現(xiàn)這樣的功能,我們可以在初始化
Navigator
的時候,在 ‘configsSence’ 中進行操作;具體操作如下:
// 設置跳轉動畫
configureScene={(route) => this.setNavAnimationType(route)}
// 設置Navigator跳轉動畫
setNavAnimationType(route) {
if (route.animationType) { // 有值
return route.animationType;
}else {
return Navigator.SceneConfigs.PushFromRight;
}
}
- 這樣我們在需要跳轉的地方只需要傳入相應的參數即可。
// 跳轉到近半小時熱門
pushToHalfHourHot() {
this.props.navigator.push({
component: HalfHourHot,
animationType:Navigator.SceneConfigs.FloatFromBottom
})
}
關閉 Navigator 返回手勢
- 上面操作后,發(fā)現(xiàn)這邊有個小細節(jié)就是我們使用了 `` 作為跳轉動畫,但是當我們下拉的時候,動畫中默認附帶的 返回手勢 會干擾我們
ListView
的滑動手勢,這個怎么解決呢?其實很簡單,我們只要關閉Navigator
手勢就可以了嘛,怎么關閉呢?其實在源碼中我們可以找到,手勢包含在動畫中,我們如果不需要,只需要給其賦值為null
,這樣它就不知道需要響應手勢事件了,方法如下:
// 設置Navigator跳轉動畫
setNavAnimationType(route) {
if (route.animationType) { // 有值
let conf = route.animationType;
conf.gestures = null; // 關閉返回手勢
return conf;
}else {
return Navigator.SceneConfigs.PushFromRight;
}
}
- 這樣我們就成功關閉了
Navigator
手勢功能。
上拉加載更多
-
react-native-pull 框架的上拉加載使用也很簡單,配合
onEndReached
、onEndReachedThreshold
、renderFooter
使用
loadMore() {
// 數據加載操作
}
renderFooter() {
return (
<View style={{height: 100}}>
<ActivityIndicator />
</View>
);
}
// 根據網絡狀態(tài)決定是否渲染 listview
renderListView() {
if (this.state.loaded === false) {
return(
<NoDataView />
);
}else {
return(
<PullList
onPullRelease={(resolve) => this.fetchData(resolve)}
dataSource={this.state.dataSource}
renderRow={this.renderRow}
showsHorizontalScrollIndicator={false}
style={styles.listViewStyle}
initialListSize={5}
renderHeader={this.renderHeader}
onEndReached={this.loadMore}
onEndReachedThreshold={60}
renderFooter={this.renderFooter}
/>
);
}
}
網絡請求基礎封裝
到這里,相信各位對 React-Native 有所熟悉了吧,從現(xiàn)在開始我們要慢慢往實際的方向走,這邊就先從網絡請求這部分開始,在正式開發(fā)中,網絡請求一般都單獨作為一部分,我們在需要使用的地方只需要簡單調用一下即可,這樣做的好處是讓整個 工程 的結構更加清晰,讓組件們各司其職,只管好自己該管的事,并且后期維護成本也會相應降低。
首先,我們要先對 fetch 的
GET
和POST
請求方式進行一層基礎封裝,也就是要把它們單獨獨立出來,那么這邊先來看下 GET 這邊:
var HTTPBase = {};
/**
*
* GET請求
*
* @param url
* @param params {}包裝
* @param headers
*
* @return {Promise}
*
* */
HTTPBase.get = function (url, params, headers) {
if (params) {
let paramsArray = [];
// 獲取 params 內所有的 key
let paramsKeyArray = Object.keys(params);
// 通過 forEach 方法拿到數組中每個元素,將元素與參數的值進行拼接處理,并且放入 paramsArray 中
paramsKeyArray.forEach(key => paramsArray.push(key + '=' + params[key]));
// 網址拼接
if (url.search(/\?/) === -1) {
url += '?' + paramsArray.join('&');
}else {
url += paramsArray.join('&');
}
}
return new Promise(function (resolve, reject) {
fetch(url, {
method:'GET',
headers:headers
})
.then((response) => response.json())
.then((response) => {
resolve(response);
})
.catch((error) => {
reject({status:-1})
})
.done();
})
}
- 好,這邊我們 GET 就封裝好了,簡單使用一下:
fetchData(resolve) {
HTTPBase.get('http://guangdiu.com/api/gethots.php')
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.data),
loaded:true,
});
if (resolve !== undefined){
setTimeout(() => {
resolve(); // 關閉動畫
}, 1000);
}
})
.catch((error) => {
})
}
export default HTTPBase;
- 接著,我們繼續(xù)來對 POST 進行封裝:
/**
*
* POST請求
*
* @param url
* @param params {}包裝
* @param headers
*
* @return {Promise}
*
* */
HTTPBase.post = function (url, params, headers) {
if (params) {
// 初始化FormData
var formData = new FormData();
// 獲取 params 內所有的 key
let paramsKeyArray = Object.keys(params);
// 通過 forEach 方法拿到數組中每個元素,將元素與參數的值進行拼接處理,并且放入 paramsArray 中
paramsKeyArray.forEach(key => formData.append(key, params[key]));
}
return new Promise(function (resolve, reject) {
fetch(url, {
method:'POST',
headers:headers,
body:formData,
})
.then((response) => response.json())
.then((response) => {
resolve(response);
})
.catch((error) => {
reject({status:-1})
})
.done();
})
}
export default HTTPBase;
- 好,來試一下:
// 網絡請求
fetchData(resolve) {
let params = {"count" : 5 };
HTTPBase.post('http://guangdiu.com/api/getlist.php', params)
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.data),
loaded:true,
});
if (resolve !== undefined){
setTimeout(() => {
resolve();
}, 1000);
}
})
.catch((error) => {
})
}
- 這次篇幅有點短,實在是太忙了!