本文共 20720 字,大约阅读时间需要 69 分钟。
前言
- 本文有配套视频,可以酌情观看。
- 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我。
- 文中所有内容仅供学习交流之用,不可用于商业用途,如因此引起的相关法律法规责任,与我无关。
- 如文中内容对您造成不便,烦请联系 277511806@qq.com 处理,谢谢。
- 转载麻烦注明出处,谢谢。
属性声明和属性确定
static propTypes = { name:PropTypes.string, ID:PropTypes.number.isRequired, } 1 2 3 4 5 1 2 3 4 5
-
上面我们声明了 name
和 ID
两个属性,并且进行了属性的确认,其中,’isRequired’ 表示如果不传递这个属性,那么开发阶段中,系统会出现警告,让我们对其进行属性确认,也就是说是否为必须属性。
-
属性确认语法分为:
React.PropTypes.any 1 2 1 2
React.PropTypes.array; React.PropTypes.func; React.PropTypes.bool; React.PropTypes.number; React.PropTypes.object; React.PropTypes.string; 1 2 3 4 5 6 7 1 2 3 4 5 6 7
React.PropTypes.element; 1 2 1 2
React.PropTypes.oneOf(['value1', 'value2']) 1 2 1 2
React.PropTypes.oneOfType([ React.PropTypes.node, React.PropTypes.number, React.PropTypes.string ]) 1 2 3 4 5 6 1 2 3 4 5 6
React.PropTypes.node; 1 2 1 2
React.PropTypes.instanceOf(NameOfClass); 1 2 1 2
React.PropTypes.arrayOf(React.PropTypes.string) 1 2 1 2
React.PropTypes..objectOf(React.PropTypes.number) 1 2 1 2
React.PropTypes.shape({ color:React.PropTypes.stirng, fontSize:React.PropTypes.number }) 1 2 3 4 5 1 2 3 4 5
static defaultProps = { name:'苍井空' }; 1 2 3 4 1 2 3 4
占位图
{/* 左边图片 */} 1 2 3 1 2 3
无数据情况处理
import React, { Component } from 'react'; import { StyleSheet, View, Text, } from 'react-native'; export default class GDNoDataView extends Component { render() { return( 无数据 ); } } const styles = StyleSheet.create({ container: { flex:1, justifyContent:'center', alignItems:'center', }, textStyle: { fontSize:21, color:'gray' } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// 根据网络状态决定是否渲染 listview renderListView() { if (this.state.loaded === false) { return( ); }else { return( this.fetchData(resolve)} dataSource={ this.state.dataSource} renderRow={ this.renderRow} showsHorizontalScrollIndicator={ false} style={styles.listViewStyle} initialListSize={ 5} /> ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
listView 头部设置
- 根据原版效果发现 提示标题 应该放到 ListView 的头部才对,所以这边就做下小修改。
1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9
// 返回 listview 头部 renderHeader() { return ( 根据每条折扣的点击进行统计,每5分钟更新一次 ); } 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9
下拉刷新
- 为了避免适配问题带来的麻烦,这边我们采用第三方框架
react-native-pull
实现下拉刷新和上拉加载更多的功能。
this.fetchData(resolve)} dataSource={ this.state.dataSource} renderRow={ this.renderRow} showsHorizontalScrollIndicator={ false} style={styles.listViewStyle} initialListSize={ 5} renderHeader={ this.renderHeader} /> 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
// 网络请求 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(); }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
网络请求之POST(重要)
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); }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
首页模块
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( { this.pushToHalfHourHot()}} > ); } // 返回中间按钮 renderTitleItem() { return( ); } // 返回右边按钮 renderRightItem() { return( { this.pushToSearch()}} > ); } // 根据网络状态决定是否渲染 listview renderListView() { if (this.state.loaded === false) { return( ); }else { return( this.fetchData(resolve)} dataSource={ this.state.dataSource} renderRow={ this.renderRow} showsHorizontalScrollIndicator={ false} style={styles.listViewStyle} initialListSize={ 5} renderHeader={ this.renderHeader} /> ); } } // 返回每一行cell的样式 renderRow(rowData) { return( ); } componentDidMount() { this.fetchData(); } render() { return ( { /* 导航栏样式 */} this.renderLeftItem()} titleItem = {() => this.renderTitleItem()} rightItem = {() => this.renderRightItem()} /> { /* 根据网络状态决定是否渲染 listview */} { this.renderListView()} ); } } 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, }, }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
- OK,这边也已经成功拿到数据,所以接着就是完成 cell 样式部分就可以了。
效果:
navigator 跳转动画
-
有时候我们需要在跳转的时候使用不同的跳转动画,比如我们 半小时热门 的跳转方式在
内叫 模态跳转,特性就是当页面退出后会直接销毁,多用于注册、登录等不需要常驻内存的界面。
-
react-native 中为了方便实现这样的功能,我们可以在初始化 Navigator
的时候,在 ‘configsSence’ 中进行操作;具体操作如下:
// 设置跳转动画configureScene={(route) => this.setNavAnimationType(route)}// 设置Navigator跳转动画 setNavAnimationType(route) { if (route.animationType) { // 有值 return route.animationType; }else { return Navigator.SceneConfigs.PushFromRight; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 1 2 3 4 5 6 7 8 9 10 11 12 13
- 这样我们在需要跳转的地方只需要传入相应的参数即可。
// 跳转到近半小时热门 pushToHalfHourHot() { this.props.navigator.push({ component: HalfHourHot, animationType:Navigator.SceneConfigs.FloatFromBottom }) } 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9
关闭 Navigator 返回手势
- 上面操作后,发现这边有个小细节就是我们使用了
` 作为跳转动画,但是当我们下拉的时候,动画中默认附带的 **返回手势** 会干扰我们
ListView的滑动手势,这个怎么解决呢?其实很简单,我们只要关闭
Navigator手势就可以了嘛,怎么关闭呢?其实在源码中我们可以找到,手势包含在动画中,我们如果不需要,只需要给其赋值为
null` ,这样它就不知道需要响应手势事件了,方法如下:
// 设置Navigator跳转动画 setNavAnimationType(route) { if (route.animationType) { // 有值 let conf = route.animationType; conf.gestures = null; // 关闭返回手势 return conf; }else { return Navigator.SceneConfigs.PushFromRight; } } 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12
- 这样我们就成功关闭了
Navigator
手势功能。
上拉加载更多
- react-native-pull 框架的上拉加载使用也很简单,配合
onEndReached
、onEndReachedThreshold
、renderFooter
使用
loadMore() { // 数据加载操作 } renderFooter() { return ( ); } // 根据网络状态决定是否渲染 listview renderListView() { if (this.state.loaded === false) { return( ); }else { return( 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} /> ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
网络请求基础封装
-
到这里,相信各位对 React-Native 有所熟悉了吧,从现在开始我们要慢慢往实际的方向走,这边就先从网络请求这部分开始,在正式开发中,网络请求一般都单独作为一部分,我们在需要使用的地方只需要简单调用一下即可,这样做的好处是让整个 工程 的结构更加清晰,让组件们各司其职,只管好自己该管的事,并且后期维护成本也会相应降低。
-
首先,我们要先对 fetch 的 GET
和 POST
请求方式进行一层基础封装,也就是要把它们单独独立出来,那么这边先来看下 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(); }) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
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; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/** * * 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; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
// 网络请求 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) => { }) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
- 这次篇幅有点短,实在是太忙了!
- http://blog.csdn.net/yeshaojian/article/details/65023875