React-Native 之 項目實戰(二)

前言


  • 本文有配套視頻,能夠酌情觀看。
  • 文中內容因各人理解不一樣,可能會有所誤差,歡迎朋友們聯繫我。
  • 文中全部內容僅供學習交流之用,不可用於商業用途,如所以引發的相關法律法規責任,與我無關。
  • 如文中內容對您形成不便,煩請聯繫 277511806@qq.com 處理,謝謝。
  • 轉載麻煩註明出處,謝謝。
  • 本篇資源:連接: https://pan.baidu.com/s/1i4HFmeT 密碼: yy79php

  • 源碼託管到 github 上,須要源碼的 點我下載,喜歡的話記得 Star,謝謝!node

屬性聲明和屬性肯定


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

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

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

static propTypes = {
        name:PropTypes.string,
        ID:PropTypes.number.isRequired,
    }
  • 上面咱們聲明瞭 nameID 兩個屬性,而且進行了屬性的確認,其中,'isRequired' 表示若是不傳遞這個屬性,那麼開發階段中,系統會出現警告,讓咱們對其進行屬性確認,也就是說是否爲必須屬性。json

  • 屬性確認語法分爲:
    • 屬性爲任何類型
    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:'蒼井空'
        };

佔位圖


  • 開發中,咱們會有許多圖片都是從網絡進行請求的,可是,若是出現網絡卡頓的狀況,圖片就會遲遲不出現,又或者有的並無圖片,這樣圖片就爲空白狀態;爲了避免讓用戶感受太突兀影響用戶體驗,也爲了視圖總體性,通常咱們會選擇使用佔位圖先展現給用戶看,等到圖片加載完畢再將圖片展現出來。react-native

  • 這邊咱們須要對cell內部進行一些處理。api

{/* 左邊圖片 */}
     <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) => {

            })
    }
  • 此次篇幅有點短,實在是太忙了!
相關文章
相關標籤/搜索