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的過程。

相關文章
相關標籤/搜索