React-Native 之 项目实战(三)
前言
- 本文有配套视频,可以酌情观看。
- 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我。
- 文中所有内容仅供学习交流之用,不可用于商业用途,如因此引起的相关法律法规责任,与我无关。
- 如文中内容对您造成不便,烦请联系 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} />
);
}
}- 思路:新建一个组件作为 Android 的启动页,index.android.js 的初始化窗口改为 Android启动页,设置定时器,使其在
git使用
项目的版本管理也是程序猿必须具备的一项技能,它能够让我们避免许多开发中遇到的尴尬问题。
公司里面一般使用 SVN 和 Git 两种,而现在 Git 的份额逐渐在蚕食着 SVN,这边我给大家提供了 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,
},
});
获取最新数据个数功能
这里需要
cnmaxid
和usmaxid
参数,他们分别是最新数据中第一个元素的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 之 项目实战(三)的更多相关文章
- React Native 之 项目实战(一)
前言 本文有配套视频,可以酌情观看. 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我. 文中所有内容仅供学习交流之用,不可用于商业用途,如因此引起的相关法律法规责任,与我无关. 如文中内容对 ...
- React Native商城项目实战04 - 封装TabNavigator.Item的创建
1.Main.js /** * 主页面 */ import React, { Component } from 'react'; import { StyleSheet, Text, View, Im ...
- React Native商城项目实战02 - 主要框架部分(tabBar)
1.安装插件,cd到项目根目录下执行: $ npm i react-native-tab-navigator --save 2.主框架文件Main.js /** * 主页面 */ import Rea ...
- React Native商城项目实战01 - 初始化设置
1.创建项目 $ react-native init BuyDemo 2.导入图片资源 安卓:把文件夹放到/android/app/src/main/res/目录下,如图: iOS: Xcode打开工 ...
- React Native商城项目实战07 - 设置“More”界面导航条
1.More/More.js /** * 更多 */ import React, { Component } from 'react'; import { AppRegistry, StyleShee ...
- React Native商城项目实战05 - 设置首页的导航条
1.Home.js /** * 首页 */ import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Te ...
- React Native商城项目实战06 - 设置安卓中的启动页
1.Main 目录下新建LaunchImage.js: /** * 启动页 */ import React, { Component } from 'react'; import { AppRegis ...
- React Native商城项目实战03 - 包装Navigator
1.在Home目录下新建首页详细页HomeDetail.js /** * 首页详情页 */ import React, { Component } from 'react'; import { App ...
- React Native商城项目实战16 - 购物中心详细页
逻辑分析: 首页(Home)加载的购物中心组件(ShopCenter),传递url数据: ShopCenter里根据url加载购物中心详细页组件(ShopCenterDetail), ShopCent ...
- React Native商城项目实战15 - 首页购物中心
1.公共的标题栏组件TitleCommonCell.js /** * 首页购物中心 */ import React, { Component } from 'react'; import { AppR ...
随机推荐
- 自己动手系列——实现一个简单的LinkedList
LinkedList与ArrayList都是List接口的具体实现类.LinkedList与ArrayList在功能上也是大体一致,但是因为两者具体的实现方式不一致,所以在进行一些相同操作的时候,其效 ...
- 【android开发笔记】为Button的背景图片添加边框式样式效果
现在做的项目遇到一个问题,设计给过来的图片只有一种状态,但是实现的需求是要求有两个状态,另一种选状态为图片背景加边框.如图: 刚开使用使用ImageView ,ImageViewButton 效果不是 ...
- Giraph入门
概要 这是一个Giraph的入门教程,主要用来运行少量输入的Giraph程序,并不能用于生产环境. 在这个教程中,我们将会在一个物理机器行部署一个单节点,伪分布的Hadoop集群.这个节点既是mast ...
- 读书笔记 effective c++ Item 17 使用单独语句将new出来的对象放入智能指针
1. 可能会出现资源泄漏的一种用法 假设我们有一个获取进程优先权的函数,还有一个在动态分类的Widget对象上根据进程优先权进行一些操作的函数: int priority(); void proces ...
- Keepalived安装与配置
下载并解压Keepalived安装包到两台nginx所在的服务器 192.168.200.1 192.168.200.2 执行编译安装(安装目录设置为 /usr/local/kee ...
- vsftp之虚拟用户
1.安装: yum install -y vsftpd yum install -y lftp2.创建用户useradd virftp -s /sbin/nologin3.创建虚拟用户及其存放路径vi ...
- jwplayer 限制拖动事件 快进 快退
开源精神不是ctrl +c + ctrl+v 谢谢 最近项目需要视频播放不能拖动,我已经实现即不能向前拖动,也不能向后拖动, 方法:打开用记事本 或者notepad 工具打开 jwpla ...
- python之数据库(mysql)操作
前言: 最近开始学django了,学了下web框架,顿时感觉又会了好多知识.happy~~ 这篇博客整理写下数据库基本操作,内容挺少.明天写SQLAlchemy. 一.数据库基本操作 1. 想允许在数 ...
- webpack 引用 jquery + bootstrap 报错解决
webpack 引用 jquery + bootstrap , error : jQuery is not defind 在webpack.dev.conf.js plugins[] 加入 new w ...
- solr笔记之solr下载及安装
在学习solr过程中,磕磕碰碰,遇到过许多问题,所以特写下笔记,以供需要的时候时常翻阅,也给能看到该博文的博友提供一个不全面的参考. 一.solr简介: Solr是一个独立的企业及搜索应用服务器,它对 ...