React-Native 之 項目實戰(zhàn)(二)

前言


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

屬性聲明和屬性確定


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

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

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

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

  • 上面我們聲明了 nameID 兩個屬性,并且進行了屬性的確認,其中,'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} />

占位圖.png

無數據情況處理


  • 還是網絡問題,在網絡出現(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}
                />
            );
        }
    }

無數據界面.png

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>
        );
    }
    
ListView頭部.gif

下拉刷新


  • 為了避免適配問題帶來的麻煩,這邊我們采用第三方框架 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();
        });
    }

下拉刷新.gif

網絡請求之POST(重要)


  • GETPOST 是我們請求 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);
        })

首頁模塊


  • 這邊我們按照前面提到的步驟,進行數據的加載。
    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 樣式部分就可以了。
首頁數據效果.gif

效果:

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跳轉動畫.gif

關閉 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返回手勢關閉.gif
  • 這樣我們就成功關閉了 Navigator 手勢功能。

上拉加載更多


  • react-native-pull 框架的上拉加載使用也很簡單,配合 onEndReachedonEndReachedThresholdrenderFooter使用
    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}
                />
            );
        }
    }
    
上拉加載更多.gif

網絡請求基礎封裝


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

  • 首先,我們要先對 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;

  • 接著,我們繼續(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) => {

            })
    }

  • 這次篇幅有點短,實在是太忙了!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容