美文网首页React-NativeReact Native
React-Native 之 项目实战(三)

React-Native 之 项目实战(三)

作者: 珍此良辰 | 来源:发表于2017-04-01 16:11 被阅读559次

    前言


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

    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启动页面.gif

    git使用


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

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

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

    错误修正 —— 模态


    • 以前看官方文档竟然没有发现 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 下载地址在上面。

    模态跳转.gif
    • 关于更详细地 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) => {
      
                  })
          }
      
    加载更多.gif

    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意思就是将 uri 参数传递到 CommunalDetail 组件:

          // 跳转到详情页
          pushToDetail(value) {
              this.props.navigator.push({
                  component:CommunalDetail,
                  params: {
                      uri: '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
              }
          });
      
    • 按照上面的方法,我们完成一下 近半小时热门模块 的跳转详情功能。

    详情页.gif

    海淘半小时热门


    • 近半小时热门 效果是一样的,只是请求参数变了,所以 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'
              }
          });
      
      
    海淘半小时热门.gif

    海淘模块


    • 我们可以发现 海淘 这一块和 首页 是类似的,只是数据请求参数不同,所以我们还是 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,
              },
          });
      
      
    海淘模块.gif

    获取最新数据个数功能


    • 这里需要 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);
          }
      
    获取最新数据个数.gif

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

    相关文章

      网友评论

      • c698662a0f45:请教下博主,我照着视频做了练习,用modal组件改写半小时热门后,打开半小时热门有数据,但是不能滑动,关闭modal,home页面就空白了,检查了代码跟视频里一模一样,不知道是什么问题。
        珍此良辰:@仓仓_700c 很高兴能帮到你
        c698662a0f45:@雨泽Forest 多谢提醒,找到了,在实战5第8节,modal放到最后就行了,再次感谢,博主牛逼:+1:
        珍此良辰:@仓仓_700c 这个bug后面章节有讲到怎么去修复,可看后面章节:smile:
      • 沐浴汐:谢谢,万分感谢,感谢你们这些提供学习资料和解答问题的大神,先赞后看,先star再下载,
        沐浴汐:@雨泽Forest :+1: :+1: :+1:
        珍此良辰:@沐熙熙 很高兴文章对你有帮助,大家都是学习者,互帮互助而已,不必太客气
      • 菜鸟程序员_:GDCommunalDetail 那个里面的webview 传入的地址key值不对 ,是uri,不是url 博主
        菜鸟程序员_:@雨泽Forest :relaxed: :relaxed:
        珍此良辰:@lingk :joy:谢谢提醒,已修改过来了!
      • 菜鸟程序员_:这里为啥要绑定啊。 renderRow={this.renderRow.bind(this)}
        珍此良辰:@lingk 因为renderRow内部使用到了this,它的调用者是ListView这个类,所以它内部的this就不是当前的this,为了解决这个问题,我们就需要使用bind对其进行绑定来解决这个问题!
      • aa035040c2a6:期待 重构:smiley:

      本文标题:React-Native 之 项目实战(三)

      本文链接:https://www.haomeiwen.com/subject/czwiottx.html