ReactNative学习实践--Navigator实践

离上次写RN笔记有一段时间了,期间参与了一个新项目,只在最近的空余时间继续学习实践,所以进度比较缓慢,不过这并不表明没有新进展,其实这个小东西离上次发文时已经有了至关大的变化了,其中影响最大的变化就是引入了Redux,后面会系统介绍一下。
在开始主题以前,先补充一点上回说到的动画初探(像我这么靠谱严谨的攻城狮,必须精益求精,┗|`O′|┛ 嗷~~),上回文说到,通过咱们本身定义了余弦动画函数以后,动态设定state的4个参数,实现了比较流畅的加载动画,这里可能有朋友已经注意到了,咱们很是频繁的调用了setState方法,这在React和RN中都是至关忌讳的,每一次setState都会触发render方法,也就意味着更频繁的虚拟DOM对比,特别是在RN中,这还意味着更频繁的JSCore<==>iOS通讯,尽管框架自己对屡次setState作了优化,好比会合并同时调用的多个setState,但这对性能和体验仍是会有较大影响,上回咱们只是单独实现了一个loading动画,因此还比较流畅,当视图中元素较多而且有各自的动画的时候,就会看到比较严重的卡顿,这些实际上是能够避免的,由于在loading动画的实现部分,咱们清楚地知道只须要loading动画的特定组成部分更新而不是组件的全部部分以及继承链上的全部组件都须要更新,而且确信这个节点必定发生了变化,所以不须要通过虚拟DOM对比,那么若是咱们能绕开setState,动画就应该会更流畅,即便在复杂的视图里边。这就是Animations文档最后提到的setNativeProps方法。javascript

As mentioned in the Direction Manipulation section, setNativeProps allows us to modify properties of native-backed components (components that are actually backed by native views, unlike composite components) directly, without having to setState and re-render the component hierarchy.java

setNativeProps容许咱们直接操纵原生组件的属性,而不须要用到setState,也不会重绘继承链上的其余组件。这正是咱们想要的效果,加上咱们明确知道正在操纵的组件以及它与视图其余组件的关系,所以,这里咱们能够放心地使用它,并且至关简单。
更新前:服务器

loopAnimation(){
    var t0=animationT,t1=t0+0.5,t2=t1+0.5,t3=t2+timeDelay,t4=t3+0.5;//这里分别是四个动画的当前时间,依次加上了0.5的延迟
    var v1=Number(Math.cos(t0).toFixed(2))*animationN+animationM;//将cos函数的小数值只精确到小数点2位,提升运算效率
    var v2=Number(Math.cos(t1).toFixed(2))*animationN+animationM;
    var v3=Number(Math.cos(t2).toFixed(2))*animationN+animationM;
    var v4=Number(Math.cos(t3).toFixed(2))*animationN+animationM;
    this.setState({
      fV:v1,
      sV:v2,
      tV:v3,
      foV:v4
    });
    animationT+=0.35;//增长时间值,每次增值越大动画越快
    requestAnimationFrame(this.loopAnimation.bind(this));
  }

更新后:框架

loopAnimation(){
    var t0=···
    var v1=···
    var v2=···
    var v3=···
    var v4=···
    this.refs.line1.setNativeProps({
      style:{width:w1,height:v1}
    });
    this.refs.line2.setNativeProps({
      style:{width:w2,height:v2}
    });
    this.refs.line3.setNativeProps({
      style:{width:w3,height:v3}
    });
    this.refs.line4.setNativeProps({
      style:{width:w4,height:v4}
    });
    animationT+=0.35;//增长时间值,每次增值越大动画越快
    requestAnimationFrame(this.loopAnimation.bind(this));
  }

效果以下:
动画效果
这里有意在注册请求完毕以后没有隐藏loading动画,所以同时执行了视图切换和loading两个动画,效果还行~ide

好了,该进入今天的正题了。先总体看一下这一阶段实现的效果(哒哒哒~):
动画效果函数

主要是模拟了一个新用户注册流程,实现起来也并不复杂,总体结构是用一个RN组件Navigator来作导航,虽然有另外一个NavigatorIOS组件在iOS系统上表现更加优异,可是考虑到RN自己但愿可以同时在安卓和iOS上运行的初衷,我选择了能够兼容两个平台的Navigator来尝试,目前来看效果还能接受。
在最后的详细信息视图里边,尝试了各类组件,好比调用相机,Switch,Slider等,主要是尝鲜,哈哈~ 也本身实现了比较简单的check按钮。
首先最外层的结构是一个Navigator,它控制整个用户注册的视图切换:oop

<Navigator style={styles.navWrap}
          initialRoute={{name: 'login', component:LoginView}}
          configureScene={(route) => {
            return Navigator.SceneConfigs.FloatFromRight;
          }}
          renderScene={(route, navigator) => {
            let Component = route.component;
            return <Component {...route.params} navigator={navigator} />
          }} />

其中,initialRoute配置了Navigator的初始组件,这里就是LoginView组件,它自己既能够直接登陆,也能够点击【我要注册】进入注册流程。configureScene属性则是用来配置Navigator中视图切换的动画类型,这里能够灵活配置切换方式:性能

Navigator.SceneConfigs.PushFromRight (default)
Navigator.SceneConfigs.FloatFromRight
Navigator.SceneConfigs.FloatFromLeft
Navigator.SceneConfigs.FloatFromBottom
Navigator.SceneConfigs.FloatFromBottomAndroid
Navigator.SceneConfigs.FadeAndroid
Navigator.SceneConfigs.HorizontalSwipeJump
Navigator.SceneConfigs.HorizontalSwipeJumpFromRight
Navigator.SceneConfigs.VerticalUpSwipeJump
Navigator.SceneConfigs.VerticalDownSwipeJump

renderScene属性则是必须配置的一个属性,它负责渲染给定路由对应的组件,也就是向Navigator全部路由对应的组件传递了"navigator"属性以及route自己携带的参数,若是不使用相似Flux或者Redux来全局存储或控制state的话,那么Navigator里数据的传递就全靠"route.params"了,好比用户注册流程中,首先是选择角色视图,而后进入注册视图填写帐号密码短信码等,此时点击注册才会将全部数据发送给服务器,所以从角色选择视图到注册视图,须要将用户选择的角色传递下去,在注册视图发送给服务器。所以,角色选择视图的跳转事件须要把参数传递下去:学习

class CharacterView extends Component {
  constructor(props){
    super(props);
    this.state={
        character:"type_one"
    }
  }

  handleNavBack(){
    this.props.navigator.pop();
  }
  
  ···
  
  handleConfirm(){
    this.props.navigator.push({
      name:"registerNav",
      component:RegisterNavView,
      params:{character:this.state.character}
    });
  }

  render(){
    return (
      <View style={styles.container}>
        <TopBarView title="注册" hasBackArr={true} onBackPress={this.handleNavBack.bind(this)}/>
        <View style={styles.main}>
          
          ···
          
          <TouchableOpacity style={styles.confirmBtn} onPress={this.handleConfirm.bind(this)}>
            <Text style={styles.confirmTxt}>确认</Text>
          </TouchableOpacity>
        </View>
      </View>
    );
  }
}

这是角色选择视图CharacterView的部分代码,因为Navigator并无像NavigatorIOS那样提供可配置的顶栏、返回按钮,因此我把顶栏作成了一个克配置的公共组件TopBarView,Navigator里边的全部视图直接使用就能够了,点击TopBarView的返回按钮时,TopBarView会调用给它配置的onBackPress回调函数,这里onBackPress回调函数是CharacterView的handleNavBack方法,即执行了:优化

this.props.navigator.pop();

关于this.props.navigator,这里咱们并无在导航链上的每一个组件显式地传递navigator属性,而是在Navigator初始化的时候就在renderScene属性方法里统一配置了,导航链上全部组件的this.props.navigator其实都指向了一个统一的navigator对象,它有两个方法:push和pop,用来向导航链压入和推出组件,视觉上就是进入下一视图和返回上一视图,所以这里当点击顶栏返回按钮时,直接调用pop方法就返回上一视图了。其实也能够把navigator对象传递到TopBarView里,在TopBarView内部调用navigator的pop方法,原理是同样的。而在CharacterView的确认按钮事件里,须要保存用户选择的角色而后跳转到下一个视图,就是经过props传递的:

this.props.navigator.push({
      name:"registerNav",
      component:RegisterNavView,
      params:{character:this.state.character}
    });

这里就是调用的navigator属性的push方法向导航链压入新的组件,即进入下一视图。push方法接收的参数是一个包含三个属性的对象,name只是用来标识组件名称,而commponent和params则是标识组件以及传递给该组件的参数对象,这里的"commponent"和"params"两个key名称是和前面renderScene是对应的,在renderScene回调里边,用到的route.commponent和route.params,就是这里push传递的参数对应的值。
在用户注册视图中,有一个用户协议须要用户确认,这里用户协议视图的切换方式与主流程不太同样,而一个Navigator只能在最初配置一种切换方式,所以,这里在Navigator里嵌套了Navigator,效果以下:
动画效果
CharacterView的跳转事件中,向navigator的push传递的组件并非RegisterView组件,而是传递的RegisterNavView组件,它是被嵌套的一个Navigator,这个子导航链上包含了用户注册视图及用户协议视图。

class RegisterNavView extends Component {
  constructor(props){
    super(props);
  }

  handleConfirm(){
    //send data to server
    ···
    //
    this.props.navigator.push({
        component:nextView,
        name:'userInfo'
      });
  }

  render(){
    return (
      <View style={styles.container}>
        <Navigator style={styles.navWrap}
          initialRoute={{name: 'register', component:RegisterView,params:{navigator:this.props.navigator,onConfirm:this.handleConfirm.bind(this)}}}
          configureScene={(route) => {
            return Navigator.SceneConfigs.FloatFromBottom;
          }}
          renderScene={(route, navigator) => {
            let Component = route.component;
            return <Component {...route.params} innerNavigator={navigator} />
          }} />
      </View>
    );
  }
}

这个被嵌套的导航咱们暂且称为InnerNav,它的初始路由组件就是RegisterView,展现了输入帐号密码等信息的视图,它的configureScene设置为“FloatFromBottom”,即从底部浮上来,renderScene也略微不同,在InnerNav导航链组件上传递的navigator对象名称改为了innerNavigator,以区别主流程Navigator,在RegisterView中有一个【用户协议】的文字按钮,在这个按钮上咱们调用了向InnerNav压入协议视图的方法:

handleShowUserdoc(){
    this.props.innerNavigator.push({
      name:"usrdoc",
      component:RegisterUsrDocView
    });
  }

而在RegisterUsrDocView即用户协议视图组件中,点击肯定按钮时咱们调用了从InnerNav推出视图的方法:

handleHideUserdoc(){
    this.props.innerNavigator.pop();
}

这样内嵌的导航链上的视图就完成了压入和推出的完整功能,若是有须要,还能够添加更多组件。
在RegisterNavView组件的handleConfirm方法中,也就是点击注册以后调用的方法,此时向服务器发送数据而且须要进入注册的下一环节了,所以须要主流程的Navigator压入新的视图,因此调用的是this.props.navigator.push,而不是innderNavigator的方法。

好了,大概结构和流程就介绍到这里了,相对比较简单,实际开发中仍是会遇到不少细节问题,好比整个注册流程中,数据都须要存储在本地,最后统一提交到服务器,若是导航链上有不少组件,那么数据就要一级一级以props的方式传递,很是蛋疼,所以才引入了Redux来统一存储和管理,下一篇文章会系统介绍Redux以及在这个小项目里引入Redux的过程。

相关文章
相关标签/搜索