zoukankan      html  css  js  c++  java
  • React-Native 之 项目实战(五)

    前言


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

    • 源码托管到 github 上,需要源码的 点我下载,喜欢的话记得 Star,谢谢!

    • 本章许多内容本来是要放到后面讲的,考虑到有朋友可能不需要了解 redux 相关内容,所以把能想到的一些常见的东西先讲下。

    小时风云榜按钮处理


    • 在服务器返回给我们的 json 数据中,提供了 hasnexthour 字段,当这个字段返回为 1 的时候,表示后面还有内容,按钮可以点击,否则不能点击,按照这个思路,我们就来完成这个功能。

    • state 中新增 isNextTouch 状态

      	isNextTouch:false   // 下一小时按钮状态
      
    • 在每次请求成功后都更新下状态:

      	let isNextTouch = true;
      
          if (responseData.hasnexthour == 1) {    // hasnexthour不为0时 下一小时 按钮可点击
              isNextTouch = false;
          }
      
          // 重新渲染
          this.setState({
              dataSource: this.state.dataSource.cloneWithRows(responseData.data),
              loaded:true,
              prompt:responseData.displaydate + responseData.rankhour + '点档' + '(' + responseData.rankduring + ')',
              isNextTouch:isNextTouch,    // 更新按钮状态
          });
      
    • 接着我们就可以根据状态进行相应更改:

      	{/* 下一小时按钮 */}
          <TouchableOpacity
              onPress={() => this.nextHour()}
              disabled={this.state.isNextTouch}
          >
              <Text style={{marginLeft:10, fontSize:17, color:this.state.isNextTouch == false ? 'green' : 'gray'}}>{"下1小时" + " >"}</Text>
          </TouchableOpacity>
      

    小时风云榜按钮处理.gif

    ref 和 setNativeProp 的使用


    • 使用 ref 可以获取到相应的组件,更加灵活地在需要的地方使用,配合 setNativeProps 可以做到不直接调用 render 直接渲染组件,在某些频繁更新的组件上使用,可以 大大提高性能,不过 千万 不要为了使用 setNativeProps 而使用,某些情况下可能会适得其反。

    • 使用方法如下:

      	
      	test() {
      		this.refs.testText.setNativeProps({
      			style: {
      				backgroundColor:'green'
      			}
      		})
      	}
      
      	<Text ref="testText">快,说我帅</Text>
      

    监听 TabBarItem 点击与传值实现 点击 Item 进行刷新功能


    • 原版 APP 中当我们点击 首页和海淘 2个 Item 时,会马上获取最新数据个数然后进行更新,这边来实现一下这个功能。

    • 通过通知的方式监听Item点击做相应的操作,所以我们在需要接收通知的页面注册一下通知,在需要的地方发送通知即可:

      • home 界面注册通知
      	// 组件加载完成
      componentDidMount() {
          // 注册通知
          this.subscription = DeviceEventEmitter.addListener('clickHomeItem', () => this.clickTabBarItem());
      }
      
      • 在页面销毁之前记得注销下:
      	componentWillUnmount() {
          // 注销通知
          this.subscription.remove();
      	}
      
      • clickTabBarItem 方法逻辑:
      	// 点击了Item
          clickTabBarItem() {
              // 加载最新数据
              this.loadData();
          }
      
    • 回到 Main 页面,我们修改下点击 Item 响应的事件:

      • 修改 Item 点击事件:
      	onPress={() => this.clickItem(selectedTab, subscription)}>
      
      • clickItem 方法逻辑:
      	// 点击了Item
          clickItem(selectedTab, subscription) {
      
              if (subscription !== "" && this.state.selectedTab == selectedTab) {
                  // 发送通知
                  DeviceEventEmitter.emit(subscription);
              }
      
              // 渲染页面
              this.setState({ selectedTab: selectedTab })
          }
      
      • 所以传值也需要新增 subscription 参数,不需要订阅的按钮就可以传 "" 即可。

      • 海淘也是类似操作,这边就不赘述,自己试着实现一下。

    每次点击 Item 获取到最新数据后我们需要及时更新 Item 角标


    • 实现思路很简单,我们使用逆传的方式,每次获取到最新数据的时候,同时需要调用一下 在 main获取最新数据个数 的请求方法即可。

      • 首先我们在 home 定义一个属性供外界使用:

        	static defaultProps = {
                loadDataNumber:{},   // 回调
            };
        
      • 接着,当我们请求到最新数据的同时,我们调用一下这个属性,就可以了:

        	// 获取最新数据个数
          this.loadDataNumber();
          
          // loadDataNumber 中的逻辑
            loadDataNumber() {
        		// 调用 this.props.loadDataNumber 中保存的代码块
                this.props.loadDataNumber();
            }
        
      • 为了方便调用,我们将 获取最新数据个数 的逻辑抽出来放到单独的方法内,这边顺便再介绍 AsyncStorage 怎么同时获取多个 key 值的方法:

        	// 获取最新数据个数网络请求
            loadDataNumber() {
                // 取出id
                AsyncStorage.multiGet(['cnfirstID', 'usfirstID'], (error, stores) => {
                    // 拼接参数
                    let params = {
                        "cnmaxid" : stores[0][1],
                        "usmaxid" : stores[1][1],
                    };
        
                    // 请求数据
                    HTTPBase.get('http://guangdiu.com/api/getnewitemcount.php', params)
                        .then((responseData) => {
                            this.setState({
                                cnbadgeText:responseData.cn,
                                usbadgeText:responseData.us
                            })
                        })
                        .catch((error) => {
        
                        })
                });
            }
        
      • 很好,接着我们转到 main 中,修改下 renderTabBarItem 方法中的内容,实现一下 `` 属性的方法:

        	renderScene={(route, navigator) => {
                let Component = route.component;
                return <Component {...route.params}
                                  navigator={navigator}
                                  loadDataNumber={() => this.loadDataNumber()} />
            }}
        
      • 到这里就完成了,海淘页面也是类似操作,自己试着实现一下。

    一键置顶功能


    • 一键置顶功能也是市面上 APP 上可以说必备功能了,这边 原版APP 也有这个功能,所以我们跟着来实现一下。

    • 这个功能实现更加简单,只要我们调用 ScrollViewscrollTo 方法,将 y 设置为 0 即可;因为 ListView 是在 ScrollView 上进行的二次开发,所以它可以使用 ScrollView 的所有方法:

      	// 点击了Item
          clickTabBarItem() {
              let PullList = this.refs.pullList;
      		// 一键置顶
              PullList.scrollTo({y:0});
         }
      
    • 这样我们的一键置顶功能就完成了,在需要的页面进行同样操作就可以实现相同功能。

    TabBarItem 逻辑完善


    • 那么为了更好的用户体验,我们这边还需要来处理一下点击 TabBarItem 的一下细节,那就是当用户点击 Item 时,可能只是单纯的想进行页面的 切换或者置顶操作 ,而不想进行 刷新,那么我们就需要来判断一下什么时候需要刷新,什么时候需要置顶。

    • 那么我们可以通过判断 ListView 中的 Scroll 的偏移量来判断是否需要进行置顶操作,当偏移量大于 1 的时候我们就进行置顶操作,否则的话我们就进行刷新操作。

    • 那么问题又来了,当我们执行刷新操作的时候,应该模拟用户下拉显示 滚动小菊花 来告诉用户我们在进行刷新操作,可是 pullList 并没有提供我们这个方法怎么办?那我们就需要分析 第三方框架的内容 来找方法解决这个问题(具体方法,可以观看我为各位录制的视频),这边就不多讲了,直接上最终代码:

      	// 点击了Item
          clickTabBarItem() {
      
              let PullList = this.refs.pullList;
      
              if (PullList.scroll.scrollProperties.offset > 0) {      // 不在顶部
                  // 一键置顶
                  PullList.scrollTo({y:0});
              }else {     // 在顶部
      
                  // 执行下拉刷新动画
                  PullList.state.pullPan = new Animated.ValueXY({x: 0, y: this.topIndicatorHeight * -1});
      
                  // 加载最新数据
                  this.loadData();
      
                  // 关闭动画
                  setTimeout(() => {
                      PullList.resetDefaultXYHandler();
                  },1000);
              }
          }
      

    点击Item最终效果.gif

    关闭筛选菜单滑动手势


    • 那这边我们的筛选菜单还有个问题,就是可以响应我们的手势进行滚动,这样肯定是不对的,那么我们需要关闭这个手势的监听,使这个菜单不能滚动,具体操作如下:

      	{/* 菜单内容 */}
          <ListView
              scrollEnabled={false}								// 关闭滑动功能
              dataSource={this.state.dataSource}                  // 设置数据源
              renderRow={this.renderRow.bind(this)}               // 根据数据初始化 Cell
              contentContainerStyle={styles.contentViewStyle}     // 样式
              initialListSize={16}                                // 一次性渲染几行数据
          />
      

    关闭筛选菜单滑动.gif


    • 到现在肯定有很多朋友发现 Navigator 跳转动画并不是那么流畅,会出现掉帧卡顿的现象,并不像 NavigatorIOS 那么丝丝顺滑;造成这个的原因是因为 NavigatorIOS 是在 UI线程 执行的 动画操作,而 Navigator 是在 JS线程执行的动画,那这样就会 阻塞住 JS线程,那么怎么去解决这个问题?这边提供 2 种方案:

      • 第一种:使用 navigation 框架,这个是目前替代 navigator 最好的方案之一,很强大,很流畅,但是需要再去学习一下使用。

      • 第二种:如果你懒得学习上面的框架,那么这边再给各位提供另一种方法 —— 使用官方提供的 API:InteractionManager(可以将一些耗时较长的工作安排到所有互动或动画完成之后再进行。这样可以保证JavaScript动画的流畅运行),这边我们就使用这种方案来进行一下优化:

        	InteractionManager.runAfterInteractions(() => {
                this.props.navigator.push({
                    component: Search,
                });
            });
        
    • 是的,就这一步操作即可,在其他需要用到 跳转功能的地方 使用一下这个API即可。

    Navigator掉帧处理.gif

    怎样调用框架中没有提供我们使用的接口


    • 这个篇幅较大,感兴趣的朋友还是参考录制的视频吧。

    removeClippedSubviews


    • 用于提升大列表的滚动性能。需要给行容器添加样式overflow:'hidden'。(Android已默认添加此样式)此属性默认开启

    • 这个属性是因为在早期 ListView 在数据到达一定程度的时候就会越来越卡,最终导致 APP 崩溃退出,使用这个属性后 APP 崩溃确实在一定程度上得到缓解,但是卡顿问题还是依旧存在。那等到后面我们会介绍 FlatList ,它将是未来 ListView 替代品,主要解决它性能差的,占用内容持续增加的问题,目前还没发布稳定版本,但是经过一段时间测试,我觉得已经可以向大家推荐了,所以在后面的章节中会为各位介绍的。

    • 废了这么多话,这边我们就先来使用一下 removeClippedSubviews ,很简单,使用它只需要在我们封装的 cell 中的 container 样式中添加 overflow:'hidden' 即可。

      	container: {
              flexDirection:'row',
              alignItems:'center',
              justifyContent:'space-between',
              backgroundColor:'white',
              height:120,
              width,
              borderBottomWidth:0.5,
              borderBottomColor:'gray',
              marginLeft:15,
              overflow:'hidden',
          },
      

    modal放置的顺序


    • 这边我们试了下安卓,发现当我们显示 modal 然后又关闭 modal 的时候,就会出现 ListView 列表消失的问题,那么其实是因为我们 modal 放置的顺序问题,modal 应当放置到所有主视图之后创建,避免它影响其他视图显示,这边就以 home 页面为例,其他视图自己实现哈:

      	render() {
              return (
                  <View style={styles.container}>
                      {/* 导航栏样式 */}
                      <CommunalNavBar
                          leftItem = {() => this.renderLeftItem()}
                          titleItem = {() => this.renderTitleItem()}
                          rightItem = {() => this.renderRightItem()}
                      />
      
                      {/* 根据网络状态决定是否渲染 listview */}
                      {this.renderListView()}
      
                      {/* 初始化近半小时热门 */}
                      <Modal pointerEvents={'box-none'}
                             animationType='slide'
                             transparent={false}
                             visible={this.state.isHalfHourHotModal}
                             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>
      
                      {/* 初始化筛选菜单 */}
                      <Modal pointerEvents={'box-none'}
                             animationType='none'
                             transparent={true}
                             visible={this.state.isSiftModal}
                             onRequestClose={() => this.onRequestClose()}
                      >
                          <CommunalSiftMenu
                              removeModal={(data) => this.closeModal(data)}
                              data={HomeSiftData}
                              loadSiftData={(mall, cate) => this.loadSiftData(mall, cate)} />
                      </Modal>
                  </View>
              );
          }
      
    • 原因我们之后会带大家来自己 开发一个类似 modal 的组件,到时候再跟大家详解。

    安卓modal问题解决.gif

    Android 加载git图动图


    • 细心的朋友应该发现了一个问题,同样一张 git 图片,在 iOS 上可以正常加载,在 Android 上图片竟然不能动,可能会想算了吧,能显示图片就行了?转头发现,产品经理正 “悠闲” 磨刀呢。。。那其实解决这个问题很简单,我们只需要使用一下 facebokk 的一个强大的图片加载库就能解决这个问题了。

    • 首先,我们打开 build.gradle ,在 dependencies 中添加下面一行代码

      	compile "com.facebook.fresco:animated-gif:0.13.0"
      
    • 重新 run 一下,编译器会自动帮我们添加这个库并配置完毕,那么 Android 上也可以愉快地显示 gif图片 了。

    • 什么?找不到这个文件?那可不行,看一下给各位录制的视频吧。

    安卓动图解决方案.gif

    导航栏返回按钮


    • 我们导航栏的返回按钮很挫对吧,这边统一简单先改一下:

      	// 返回左边按钮
          renderLeftItem() {
              return(
                  <TouchableOpacity
                      onPress={() => {this.pop()}}
                  >
                      <View style={{flexDirection:'row', alignItems:'center'}}>
                          <Image source={{uri:'back'}} style={styles.navbarLeftItemStyle} />
                          <Text>返回</Text>
                      </View>
      
                  </TouchableOpacity>
              );
          }
      

    导航栏返回按钮样式.gif

    去除 Android 中输入框的下划线


    • 那么 Android 中的 TextInput 的下划线是不是丑爆了?这边我们也来处理下它,直接使用 underlineColorAndroid 这个属性,让他为透明即可。

      	underlineColorAndroid={'transparent'}
      

    去除安卓输入框下划线.gif


    • 这边先来介绍一下 navigationBa 的使用,使用它可以让我们只在一个地方管理 navigator 导航栏的样式,就不用像现在这样在每个页面都手动添加导航栏。

    • 这边先举个例子让大家知道怎么用,考虑到传值不方便的原因,到讲完 redux 之后,再一起讲这部分内容,所以大家只需要了解一下怎么使用就可以了。

    • 首先,我们来看下 navigationBar 文件内的内容:

      	let NavigationBarRouteMapper = {
      	    LeftButton(route, navigator, index, navState) {
      	        if (index > 0) {
      	            return (
      	                <TouchableOpacity
      	                    onPress={() => navigator.pop()}
      	                >
      	                    <Text>返回</Text>
      	                </TouchableOpacity>
      	            )
      	        }
      	    },
      	
      	    RightButton(route, navigator, index, navState) {
      	
      	    },
      	
      	    Title(route, navigator, index, navState) {
      	        return(
      	            <Text>{route.name}</Text>
      	        )
      	    },
      	};
      	
      	export default (
      	    <Navigator.NavigationBar
      	        style={{backgroundColor:'green'}}
      	        routeMapper={NavigationBarRouteMapper}
      	    />
      	)
      
    • 接着,我们到 main文件中 使用一下这个 navigationBar

      	navigationBar={NavigationBar}
      

    navigationBar.gif

    react-native 开发中你可能需要的一些小玩意


    • 拨打电话(真机测试,模拟器没有打电话功能):

      	import { Linking } from ‘react-native’;
      	
      	function callPhone() {
      		return Linking.openURL('tel:10086');
      	}
      
    • 获取视图组件的 x,y,宽,高,偏移量的值,可以使用 measure 方法:

      	this.refs.mainView.measure((x, y, width, height, px,py)) => {
      		console.log(width);
      	}
      	
      
    • 开发中建议先从 iOS 端做起,安卓端适配;当然如果公司不是只有你一个人负责 react-native 项目,大可不必理会这条。

    • 开发中有些功能在 模拟器 上是无法测试的,这时候需要配合真机进行调试,下面整理出一些常见的问题:

      • 当 Android 提示找不到服务器时:

        • 确定电脑与我们手机连接同一个 WiFi 网络环境下。

        • 我们需要打开 开发中菜单(摇下手机) —— —— —— 输入电脑IP地址(IP地址怎么找?自己搜索吧)—— 退出菜单 ——

      • 当 iOS 提示找不到服务器时:

        • 打开 Xcode —— AppDelegate.m 文件 —— 更改 jsCodeLocation 中的 localhost 为电脑的IP地址 —— 重新运行一遍。

    优化工具介绍与使用


    • 本来要讲的,但是发现现在的项目还没出现太明显性能问题,不好测试,就放到最后面讲吧。

    第一版完结


    • 到这里第一个版本就完结了,接下来就要开始我们的第二版本的开发了,那么第二个版本之前大概会用一篇的内容来主要讲下 react 怎么结合 redux 进行开发,并做个小 Demo,让大家先熟悉一下 redux 使用,然后就是我们当前的项目 转为 redux 开发了。

    • 这边想知道大家更想知道或者对于 redux 哪里比较不理解的,好跟着改进一下,尽量使文章和视频更易懂。

  • 相关阅读:
    bat 实现主机hostname的修改
    我的影视作品,如何在博客园发布带有CSS样式的HTML
    java实现Windows记事本
    cmd 执行Dcpromo错误:在该 SKU 上不支持 Active Directory 域服务安装向导,Windows Server 2008 R2 Enterprise 配置AD(Active Directory)域控制器
    警告: 正保存的数据被裁断到 1024 字符。 错误: 拒绝访问注册表路径。
    山西大同大学教务处教师端——可在PC端,手机端操作
    bat脚本+diskpart 脚本实现自动划分磁盘分区
    山西大同大学教务处学生端--送给学弟,学妹的礼物,可在PC端,手机端操作
    ECharts
    javascript删除数组里的对象
  • 原文地址:https://www.cnblogs.com/miaomiaoshen/p/6713776.html
Copyright © 2011-2022 走看看