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) => {

            })
    }
  • 此次篇幅有点短,实在是太忙了!
相关文章
相关标签/搜索