React-Native 之 項目實戰(二)


前言


  • 本文有配套視頻,可以酌情觀看。
  • 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯系我。
  • 文中所有內容僅供學習交流之用,不可用於商業用途,如因此引起的相關法律法規責任,與我無關。
  • 如文中內容對您造成不便,煩請聯系 277511806@qq.com 處理,謝謝。
  • 轉載麻煩注明出處,謝謝。

屬性聲明和屬性確定


  • 有朋友反饋這邊 屬性聲明和屬性確定 不了解,這邊就來補充一下。

  • React-Native 創建的自定義組件是可以復用的,而開發過程中一個組件可能會由多個人同時開發或者多個人使用一個組件,為了讓開發人員之間減少溝通成本,我們會對某些必要的屬性進行屬性聲明,讓使用的人知道需要傳入什么!甚至有些需要傳入但沒有傳入值的屬性我們會進行警告處理!

  • 這邊先來看下 屬性聲明 的示例:

	static propTypes = {
		name:PropTypes.string,
		ID:PropTypes.number.isRequired,
	}

  • 上面我們聲明了 nameID 兩個屬性,並且進行了屬性的確認,其中,'isRequired' 表示如果不傳遞這個屬性,那么開發階段中,系統會出現警告,讓我們對其進行屬性確認,也就是說是否為必須屬性。

  • 屬性確認語法分為:

    • 屬性為任何類型
    	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
    	])
    
    
    • 屬性為可渲染的節點
    	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:'蒼井空'
    	};
    
    

占位圖


  • 開發中,我們會有許多圖片都是從網絡進行請求的,但是,如果出現網絡卡頓的情況,圖片就會遲遲不出現,又或者有的並沒有圖片,這樣圖片就為空白狀態;為了不讓用戶感覺太突兀影響用戶體驗,也為了視圖整體性,一般我們會選擇使用占位圖先展示給用戶看,等到圖片加載完畢再將圖片展示出來。

  • 這邊我們需要對cell內部進行一些處理。

	 {/* 左邊圖片 */}
     <Image source={{uri:this.props.image === '' ? 'defaullt_thumb_83x83' : this.props.image}} style={styles.imageStyle} />

占位圖.png

無數據情況處理


  • 還是網絡問題,在網絡出現問題或者無法加載數據的時候,一般我們會展示空白頁,在空白頁中提示 無數據 之類的提示,比較好的還會使用 指示器 的方式告訴用戶網絡出現問題等等。

  • 這邊我們做以下處理,當無數據時,我們就先初始化基礎界面,然后展示 提示 頁面,等到有數據時,再重新渲染數據。

  • 首先設置 無數據 頁面

	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'
    	}
	});
	
  • 接着,沒有數據的時候我們進行一些處理就可以了
	// 根據網絡狀態決定是否渲染 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}
                />
            );
        }
    }

無數據界面.png

listView 頭部設置


  • 根據原版效果發現 提示標題 應該放到 ListView 的頭部才對,所以這邊就做下小修改。
	<ListView
             dataSource={this.state.dataSource}
             renderRow={this.renderRow}
             showsHorizontalScrollIndicator={false}
             style={styles.listViewStyle}
             initialListSize={5}
             renderHeader={this.renderHeader}
   />

  • renderHeader 方法實現
	// 返回 listview 頭部
    renderHeader() {
        return (
            <View style={styles.headerPromptStyle}>
                <Text>根據每條折扣的點擊進行統計,每5分鍾更新一次</Text>
            </View>
        );
    }
    

ListView頭部.gif

下拉刷新


  • 為了避免適配問題帶來的麻煩,這邊我們采用第三方框架 react-native-pull 實現下拉刷新和上拉加載更多的功能。
	<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();
        });
    }

下拉刷新.gif

網絡請求之POST(重要)


  • GETPOST 是我們請求 HTTP 接口常用的方式,針對表單提交的請求,我們通常采用 POST 的方式。

  • JQuery 中,傳入對象框架會自動封裝成 formData 的形式,但是在 fetch 中沒有這個功能,所以我們需要自己初始化一個 FormData 直接傳給 body (補充:FormData也可以傳遞字節流實現上傳圖片功能)。

	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);
		})

首頁模塊


  • 這邊我們按照前面提到的步驟,進行數據的加載。
	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);
	        // 初始狀態
	        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>
	        );
	    }
	
	    // 根據網絡狀態決定是否渲染 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()}
	                />
	
	                {/* 根據網絡狀態決定是否渲染 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 樣式部分就可以了。

首頁數據效果.gif

效果:


  • 有時候我們需要在跳轉的時候使用不同的跳轉動畫,比如我們 半小時熱門 的跳轉方式在 iOS 內叫 模態跳轉,特性就是當頁面退出后會直接銷毀,多用於注冊、登錄等不需要常駐內存的界面。

  • react-native 中為了方便實現這樣的功能,我們可以在初始化 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跳轉動畫.gif

關閉 Navigator 返回手勢

  • 上面操作后,發現這邊有個小細節就是我們使用了 `` 作為跳轉動畫,但是當我們下拉的時候,動畫中默認附帶的 返回手勢 會干擾我們 ListView 的滑動手勢,這個怎么解決呢?其實很簡單,我們只要關閉Navigator 手勢就可以了嘛,怎么關閉呢?其實在源碼中我們可以找到,手勢包含在動畫中,我們如果不需要,只需要給其賦值為 null ,這樣它就不知道需要響應手勢事件了,方法如下:

	// 設置Navigator跳轉動畫
    setNavAnimationType(route) {
        if (route.animationType) {  // 有值
            let conf = route.animationType;
            conf.gestures = null;   // 關閉返回手勢
            return conf;
        }else {
            return Navigator.SceneConfigs.PushFromRight;
        }
    }

navigator返回手勢關閉.gif

  • 這樣我們就成功關閉了 Navigator 手勢功能。

上拉加載更多


  • react-native-pull 框架的上拉加載使用也很簡單,配合 onEndReachedonEndReachedThresholdrenderFooter 使用
	loadMore() {
        // 數據加載操作
    }

    renderFooter() {
        return (
            <View style={{height: 100}}>
                <ActivityIndicator />
            </View>
        );
    }

    // 根據網絡狀態決定是否渲染 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}
                />
            );
        }
    }
	

上拉加載更多.gif

網絡請求基礎封裝


  • 到這里,相信各位對 React-Native 有所熟悉了吧,從現在開始我們要慢慢往實際的方向走,這邊就先從網絡請求這部分開始,在正式開發中,網絡請求一般都單獨作為一部分,我們在需要使用的地方只需要簡單調用一下即可,這樣做的好處是讓整個 工程 的結構更加清晰,讓組件們各司其職,只管好自己該管的事,並且后期維護成本也會相應降低。

  • 首先,我們要先對 fetchGETPOST 請求方式進行一層基礎封裝,也就是要把它們單獨獨立出來,那么這邊先來看下 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;

  • 接着,我們繼續來對 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) => {

            })
    }

  • 這次篇幅有點短,實在是太忙了!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM