React-Native 之 项目实战(三)

react

前言


  • 本文有配套视频,可以酌情观看。
  • 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我。
  • 文中所有内容仅供学习交流之用,不可用于商业用途,如因此引起的相关法律法规责任,与我无关。
  • 如文中内容对您造成不便,烦请联系 277511806@qq.com 处理,谢谢。
  • 转载麻烦注明出处,谢谢。

  • 本篇资源:链接: https://pan.baidu.com/s/1jIbW2n8 密码: wqe4

  • 从这篇开始我们就将源码托管到 github 上,需要源码的 点我下载,喜欢的话记得 Star,谢谢!

Android启动页面


  • 从上面的效果可以看出,安卓端还没有启动页面,这边我们就通过React-Native 的方式解决。

    • 思路:新建一个组件作为 Android 的启动页,index.android.js 的初始化窗口改为 Android启动页,设置定时器,使其在1.5秒后自动跳转到 Main 组件。

    export default class GDLaunchPage extends Component {

    componentDidMount() {

    setTimeout(() => {

    this.props.navigator.replace({

    component:Main

    })

    }, 1500)

    }

    render() {

    return(

    <Image source={{uri:'launchimage'}} style={styles.imageStyle} />

    );

    }

    }

git使用


  • 项目的版本管理也是程序猿必须具备的一项技能,它能够让我们避免许多开发中遇到的尴尬问题。

  • 公司里面一般使用 SVN 和 Git 两种,而现在 Git 的份额逐渐在蚕食着 SVN,这边我给大家提供了 SVN 和 Git 的详情版,大家可以前往阅读。

    • svn介绍与使用
    • git介绍与使用

  • 这小结建议观看视频,视频内有具体操作!

错误修正 —— 模态


  • 以前看官方文档竟然没有发现 React-Native 提供了 model 组件,在这里给大家道个歉,以后跪着写教程,不用让我起来,反正我感觉膝盖软软的!

  • 前几天在看官方文档的时候,无意中看见 model 组件,我嘞个天,有这东西就可以减少开发中很多功能开发难度。当初怎么没发现,还傻傻地一步一步去封装这个东西 T^T,在这告诫各位,不能太粗心!

  • 这边我们就将原本 近半小时热门 这个模块的跳转模式改成 正宗的 模态,代码如下:

	render() {

return (

<View style={styles.container}>

{/* 初始化模态 */}

<Modal

animationType='slide'

transparent={false}

visible={this.state.isModal}

onRequestClose={() => this.onRequestClose()}

>

<Navigator

initialRoute={{

name:'halfHourHot',

component:HalfHourHot

}}

renderScene={(route, navigator) => {

let Component = route.component;

return <Component

removeModal={(data) => this.closeModal(data)}

{...route.params}

navigator={navigator} />

}} />

</Modal>

{/* 导航栏样式 */}

<CommunalNavBar

leftItem = {() => this.renderLeftItem()}

titleItem = {() => this.renderTitleItem()}

rightItem = {() => this.renderRightItem()}

/>

{/* 根据网络状态决定是否渲染 listview */}

{this.renderListView()}

</View>

);

}

注:这边需要注意一下 逆向传值 的方式,这里用到最基本的逐层传值,类似于 block 的功能,具体的代码参考 Demo , Demo 下载地址在上面。

  • 关于更详细地 model 使用,可以参照官方文档 model ,当然我也给各位上了这道菜 —— React-Native 之 model介绍与使用 。

  • 通过查看 modal 的源码,我们不难发现 —— 其实 modal 实现原理也只是使用了 绝对定位,所以如果 modal 无法满足我们的功能,我们可以使用 绝对定位 来自己实现一下类似功能。

加载更多功能完善


  • 这边我们来完善一下 加载更多功能数据 的加载,需要注意的一点就是,拼接数组需要使用 concat 方法来拼接,它会返回一个 新的数组 给我们使用,而不修改传入的数组。

  • 这边我们加载数据的方法分为 2 个,代码看起来重复性很高,但是其实这就取决于我们的需求了,我们分为 2 个的好处是看起来更清晰,减少沟通成本,想象一下,如果我们把所有逻辑都放到同一个方法内,那么是不是这个方法内的逻辑是不是特别复杂,不方便后期维护?!所以这就是为什么分为 2 个方法进行加载的原因。

  • 那来看一下加载最新数据这边逻辑:

    	// 加载最新数据网络请求

    loadData(resolve) {

    let params = {"count" : 10 };

    HTTPBase.get('https://guangdiu.com/api/getlist.php', params)

    .then((responseData) => {

    // 清空数组

    this.data = [];

    // 拼接数据

    this.data = this.data.concat(responseData.data);

    // 重新渲染

    this.setState({

    dataSource: this.state.dataSource.cloneWithRows(this.data),

    loaded:true,

    });

    // 关闭刷新动画

    if (resolve !== undefined){

    setTimeout(() => {

    resolve();

    }, 1000);

    }

    // 存储数组中最后一个元素的id

    let cnlastID = responseData.data[responseData.data.length - 1].id;

    AsyncStorage.setItem('cnlastID', cnlastID.toString());

    })

    .catch((error) => {

    })

    }

  • 再来看下加载更多这边的逻辑:

    • 加载更多需要在获取 最新 数据的时候将数组中 最后一个元素 内的ID保存起来,因为不是大批量数据存储,这边我们就使用 AsyncStorage 进行 id 的存储。

    • 接着,我们拼接请求参数。

    // 加载更多数据的网络请求

    loadMoreData(value) {

    let params = {

    "count" : 10,

    "sinceid" : value

    };

    HTTPBase.get('https://guangdiu.com/api/getlist.php', params)

    .then((responseData) => {

    // 拼接数据

    this.data = this.data.concat(responseData.data);

    this.setState({

    dataSource: this.state.dataSource.cloneWithRows(this.data),

    loaded:true,

    });

    // 存储数组中最后一个元素的id

    let cnlastID = responseData.data[responseData.data.length - 1].id;

    AsyncStorage.setItem('cnlastID', cnlastID.toString());

    })

    .catch((error) => {

    })

    }

Cell 点击实现


  • 我们回到主页这边来实现以下 cell 的点击,需要注意的是对 row 进行绑定操作,不然会找不到当前的 this

    	// 绑定

    renderRow={this.renderRow.bind(this)}

  • 接着来看下 renderRow 方法实现:

    	// 返回每一行cell的样式

    renderRow(rowData) {

    return(

    <TouchableOpacity

    onPress={() => this.pushToDetail(rowData.id)}

    >

    <CommunalHotCell

    image={rowData.image}

    title={rowData.title}

    />

    </TouchableOpacity>

    );

    }

  • 再来看下 pushToDetail 方法实现,params意思就是将 url 参数传递到 CommunalDetail 组件:

    	// 跳转到详情页

    pushToDetail(value) {

    this.props.navigator.push({

    component:CommunalDetail,

    params: {

    url: 'https://guangdiu.com/api/showdetail.php' + '?' + 'id=' + value

    }

    })

    }

详情页


  • 既然我们已经保存了 id 那么就可以来做详情页了,当我们点击 cell 的时候,需要跳转到对应的 详情页 。

  • 这边服务器返回给我们的是个 网页数据 ,我们这边就直接使用 webView组件 展示,具体使用我们就不多做介绍了,很简单,详情就参考官方文档 WebView。

  • 先来看详情页的实现:

    	export default class GDCommunalDetail extends Component {

    static propTypes = {

    uri:PropTypes.string,

    };

    // 返回

    pop() {

    this.props.navigator.pop();

    }

    // 返回左边按钮

    renderLeftItem() {

    return(

    <TouchableOpacity

    onPress={() => {this.pop()}}

    >

    <Text>返回</Text>

    </TouchableOpacity>

    );

    }

    componentWillMount() {

    // 发送通知

    DeviceEventEmitter.emit('isHiddenTabBar', true);

    }

    componentWillUnmount() {

    // 发送通知

    DeviceEventEmitter.emit('isHiddenTabBar', false);

    }

    render() {

    return(

    <View style={styles.container}>

    {/* 导航栏 */}

    <CommunalNavBar

    leftItem = {() => this.renderLeftItem()}

    />

    {/* 初始化WebView */}

    <WebView

    style={styles.webViewStyle}

    source={{url:this.props.url, method: 'GET' }}

    javaScriptEnabled={true}

    domStorageEnabled={true}

    scalesPageToFit={false}

    />

    </View>

    );

    }

    }

    const styles = StyleSheet.create({

    container: {

    flex:1

    },

    webViewStyle: {

    flex:1

    }

    });

  • 按照上面的方法,我们完成一下 近半小时热门模块 的跳转详情功能。

海淘半小时热门


  • 和 近半小时热门 效果是一样的,只是请求参数变了,所以 Copy 然后修改下相应参数啊:

    	export default class GDUSHalfHourHot 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);

    }

    static defaultProps = {

    removeModal:{}

    }

    // 网络请求

    fetchData(resolve) {

    let params = {

    "c" : "us"

    };

    HTTPBase.get('http://guangdiu.com/api/gethots.php', params)

    .then((responseData) => {

    this.setState({

    dataSource: this.state.dataSource.cloneWithRows(responseData.data),

    loaded:true,

    });

    if (resolve !== undefined){

    setTimeout(() => {

    resolve(); // 关闭动画

    }, 1000);

    }

    })

    .catch((error) => {

    })

    }

    popToHome(data) {

    this.props.removeModal(data);

    }

    // 返回中间按钮

    renderTitleItem() {

    return(

    <Text style={styles.navbarTitleItemStyle}>近半小时热门</Text>

    );

    }

    // 返回右边按钮

    renderRightItem() {

    return(

    <TouchableOpacity

    onPress={()=>{this.popToHome(false)}}

    >

    <Text style={styles.navbarRightItemStyle}>关闭</Text>

    </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.bind(this)}

    showsHorizontalScrollIndicator={false}

    style={styles.listViewStyle}

    initialListSize={5}

    renderHeader={this.renderHeader}

    />

    );

    }

    }

    // 返回 listview 头部

    renderHeader() {

    return (

    <View style={styles.headerPromptStyle}>

    <Text>根据每条折扣的点击进行统计,每5分钟更新一次</Text>

    </View>

    );

    }

    // 跳转到详情页

    pushToDetail(value) {

    this.props.navigator.push({

    component:CommunalDetail,

    params: {

    url: 'https://guangdiu.com/api/showdetail.php' + '?' + 'id=' + value

    }

    })

    }

    // 返回每一行cell的样式

    renderRow(rowData) {

    return(

    <TouchableOpacity

    onPress={() => this.pushToDetail(rowData.id)}

    >

    <CommunalHotCell

    image={rowData.image}

    title={rowData.title}

    />

    </TouchableOpacity>

    );

    }

    componentWillMount() {

    // 发送通知

    DeviceEventEmitter.emit('isHiddenTabBar', true);

    }

    componentWillUnmount() {

    // 发送通知

    DeviceEventEmitter.emit('isHiddenTabBar', false);

    }

    componentDidMount() {

    this.fetchData();

    }

    render() {

    return (

    <View style={styles.container}>

    {/* 导航栏样式 */}

    <CommunalNavBar

    titleItem = {() => this.renderTitleItem()}

    rightItem = {() => this.renderRightItem()}

    />

    {/* 根据网络状态决定是否渲染 listview */}

    {this.renderListView()}

    </View>

    );

    }

    }

    const styles = StyleSheet.create({

    container: {

    flex:1,

    alignItems: 'center',

    },

    navbarTitleItemStyle: {

    fontSize:17,

    color:'black',

    marginLeft:50

    },

    navbarRightItemStyle: {

    fontSize:17,

    color:'rgba(123,178,114,1.0)',

    marginRight:15

    },

    listViewStyle: {

    width:width,

    },

    headerPromptStyle: {

    height:44,

    width:width,

    backgroundColor:'rgba(239,239,239,0.5)',

    justifyContent:'center',

    alignItems:'center'

    }

    });

海淘模块


  • 我们可以发现 海淘 这一块和 首页 是类似的,只是数据请求参数不同,所以我们还是 Copy 一下代码,然后将请求参数改为如下:

    	export default class GDHome extends Component {

    // 构造

    constructor(props) {

    super(props);

    // 初始状态

    this.state = {

    dataSource: new ListView.DataSource({rowHasChanged:(r1, r2) => r1 !== r2}),

    loaded:false,

    isModal:false

    };

    this.data = [];

    this.loadData = this.loadData.bind(this);

    this.loadMore = this.loadMore.bind(this);

    }

    // 加载最新数据网络请求

    loadData(resolve) {

    let params = {

    "count" : 10,

    "country" : "us"

    };

    HTTPBase.get('https://guangdiu.com/api/getlist.php', params)

    .then((responseData) => {

    // 拼接数据

    this.data = this.data.concat(responseData.data);

    // 重新渲染

    this.setState({

    dataSource: this.state.dataSource.cloneWithRows(this.data),

    loaded:true,

    });

    // 关闭刷新动画

    if (resolve !== undefined){

    setTimeout(() => {

    resolve();

    }, 1000);

    }

    // 存储数组中最后一个元素的id

    let uslastID = responseData.data[responseData.data.length - 1].id;

    AsyncStorage.setItem('uslastID', uslastID.toString());

    })

    .catch((error) => {

    })

    }

    // 加载更多数据的网络请求

    loadMoreData(value) {

    let params = {

    "count" : 10,

    "sinceid" : value,

    "country" : "us"

    };

    HTTPBase.get('https://guangdiu.com/api/getlist.php', params)

    .then((responseData) => {

    // 拼接数据

    this.data = this.data.concat(responseData.data);

    this.setState({

    dataSource: this.state.dataSource.cloneWithRows(this.data),

    loaded:true,

    });

    // 存储数组中最后一个元素的id

    let uslastID = responseData.data[responseData.data.length - 1].id;

    AsyncStorage.setItem('uslastID', uslastID.toString());

    })

    .catch((error) => {

    })

    }

    // 加载更多数据操作

    loadMore() {

    // 读取id

    AsyncStorage.getItem('uslastID')

    .then((value) => {

    // 数据加载操作

    this.loadMoreData(value);

    })

    }

    // 模态到近半小时热门

    pushToHalfHourHot() {

    this.setState({

    isModal:true

    })

    }

    // 跳转到搜索

    pushToSearch() {

    this.props.navigator.push({

    component:Search,

    })

    }

    // 安卓模态销毁处理

    onRequestClose() {

    this.setState({

    isModal:false

    })

    }

    // 关闭模态

    closeModal(data) {

    this.setState({

    isModal:data

    })

    }

    // 返回左边按钮

    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尾部

    renderFooter() {

    return (

    <View style={{height: 100}}>

    <ActivityIndicator />

    </View>

    );

    }

    // 根据网络状态决定是否渲染 listview

    renderListView() {

    if (this.state.loaded === false) {

    return(

    <NoDataView />

    );

    }else {

    return(

    <PullList

    onPullRelease={(resolve) => this.loadData(resolve)}

    dataSource={this.state.dataSource}

    renderRow={this.renderRow.bind(this)}

    showsHorizontalScrollIndicator={false}

    style={styles.listViewStyle}

    initialListSize={5}

    renderHeader={this.renderHeader}

    onEndReached={this.loadMore}

    onEndReachedThreshold={60}

    renderFooter={this.renderFooter}

    />

    );

    }

    }

    // 跳转到详情页

    pushToDetail(value) {

    this.props.navigator.push({

    component:CommunalDetail,

    params: {

    url: 'https://guangdiu.com/api/showdetail.php' + '?' + 'id=' + value

    }

    })

    }

    // 返回每一行cell的样式

    renderRow(rowData) {

    return(

    <TouchableOpacity

    onPress={() => this.pushToDetail(rowData.id)}

    >

    <CommunalHotCell

    image={rowData.image}

    title={rowData.title}

    />

    </TouchableOpacity>

    );

    }

    componentDidMount() {

    this.loadData();

    }

    render() {

    return (

    <View style={styles.container}>

    {/* 初始化模态 */}

    <Modal

    animationType='slide'

    transparent={false}

    visible={this.state.isModal}

    onRequestClose={() => this.onRequestClose()}

    >

    <Navigator

    initialRoute={{

    name:'halfHourHot',

    component:USHalfHourHot

    }}

    renderScene={(route, navigator) => {

    let Component = route.component;

    return <Component

    removeModal={(data) => this.closeModal(data)}

    {...route.params}

    navigator={navigator} />

    }} />

    </Modal>

    {/* 导航栏样式 */}

    <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,

    },

    });

获取最新数据个数功能


  • 这里需要 cnmaxidusmaxid 参数,他们分别是最新数据中第一个元素的 id,也就是我们每次 刷新 的时候都保存一下数组中的第一个元素的 id

    	 // 首页存储数组中第一个元素的id

    let cnfirstID = responseData.data[0].id;

    AsyncStorage.setItem('cnfirstID', cnfirstID.toString());

  • 这个功能是从程序启动的时候就开始 定时循环执行 ,也就是我们需要放到 入口文件中(Main文件)。

    	componentDidMount() {

    // 注册通知

    this.subscription = DeviceEventEmitter.addListener('isHiddenTabBar', (data)=>{this.tongZhi(data)});

    // 声明变量

    let cnfirstID = 0;

    let usfirstID = 0;

    // 最新数据的个数

    setInterval(() => {

    // 取出id

    AsyncStorage.getItem('cnfirstID')

    .then((value) => {

    cnfirstID = parseInt(value);

    });

    AsyncStorage.getItem('usfirstID')

    .then((value) => {

    usfirstID = parseInt(value);

    });

    if (cnfirstID !== 0 && usfirstID !== 0) { // 参数不为0

    // 拼接参数

    let params = {

    "cnmaxid" : cnfirstID,

    "usmaxid" : usfirstID

    };

    // 请求数据

    HTTPBase.get('http://guangdiu.com/api/getnewitemcount.php', params)

    .then((responseData) => {

    console.log(responseData);

    this.setState({

    cnbadgeText:responseData.cn,

    usbadgeText:responseData.us

    })

    })

    }

    }, 30000);

    }

注:上面使用到的 setInterval 也是个定时器,和我们之前使用的 setTimeout 不同的是,setInterval 是周期定时器,比如上面时间为 30000毫秒,意思就是每过 30000毫秒 就会执行一次里面的代码。而 setTimeout 则是会在规定的时间后 尽快 执行任务。

以上是 React-Native 之 项目实战(三) 的全部内容, 来源链接: utcz.com/z/383444.html

回到顶部