react-navigation路由篇之StackRouter

react-navigation的基礎認識:

react-navigation是官方正在推的導航,可是沒有放入到react-native包中,而是單獨開了一個庫,它的核心概念是Router,Router的概念圖以下所示:
react

routers-concept-map.png
routers-concept-map.png

最上方的文字:
上面這個圖清晰的表達出了Router能幹什麼,最重要的一句話是:Router常常組合使用,而且能代理子路由,這句話的意思我待會分析源碼來深刻了解。android

上圖的右部分:
由Action而引發State的變化,是否是很像Redux?後面我會寫篇文章專門寫如何配合Redux自定義行爲。在不配合Redux使用時,它本身內部其實也經過createNavigationContainer(後邊源碼分析會說到)來做爲主容器維護這相似Redux形式的數據流。用戶在使用App的過程當中觸發導航Action,例如StackRouter的Navigate,goBack等,這些Action被Dispatch出去後會被router.getStateForAction(action, lastState)處理,getStateForAction經過Action和以前的State得到新的State。這就是一個完整的數據流過程。git

上圖的左部分:
除了在App運行過程當中用戶主動觸發goBack,navigate這些Action外,當App不在運行時,也能夠經過推送,通知等喚醒App來派發Action,router.getActionForPathAndParams經過path和params能夠取得Action,而後就能夠走圖右部分的流程了。github

由上面的分析能夠知道,router是用來管理導航狀態,它的原理就是經過派發Action來得到新的State,從而維護和管理導航的狀態,導航的State的結構以下:redux

//MainTabState:
{
  index: 0,
  routes: [
    {
      key: 'key-1',
      routeName: 'Home',
      ...
    },
    {
      key: 'key-2',
      routeName: 'SomeTab',
      index: 2,
      routes:[...]
    },
    {
      key: 'key-3',
      routeName: 'Mine',
      ...
    }
  ]
}複製代碼

MainTab有3個Tab,分別是Home,SomeTab, Mine,當前MainTab停留在Home上,SomeTab也是一個TabNavigator,它停留在第3個Tab上(index從0開始)。index表示導航的當前位置,routes表示導航中已經存在的screen,每一個screen都有惟一的key標識,screen也能夠是Navigator,好比SomeTab。react-native

上面對Router有了大概的瞭解,注意是Router,由於後面會說到route,常常會混淆。Router是用來管理導航的State,是導航的Core,StackNavigator的navigate,back操做的邏輯都是由StackRouter處理的,TabNavigator的navigate,back操做的邏輯都是由TabRouter處理的。用Router的State來渲染當前Screen,管理手勢和動畫的任務,就要配合Navigation View和Transitioner了。
Navigation View
Transitionerbash


說在前邊的話

這篇文章是分析導航路由的實現原理,而不是講解react-navigation的基礎用法,須要瞭解react-navigation的基礎用法,請移步官網:reactnavigation.org/。函數


深刻淺出Router

什麼是Router?Router是Navigator的核心,有了Router就能夠自定義Navigator了:源碼分析

class MyNavigator extends React.Component {
    static router = StackRouter(routes, config);
    ...
}複製代碼

react-navigation有兩個內建的Router,分別是StackRouterTabRouter,它們都提供了一套基礎的Router API:學習

  • getStateForAction(action, state)
  • getComponentForRouteName(routeName)
  • getComponentForState(state)
  • getActionForPathAndParams(path, params)
  • getPathAndParamsForState(state)
  • getScreenOptions(navigation, screenProps)

StackNavigator:

用法:

const ModalStack = StackNavigator({
  Home: {
    screen: MyHomeScreen,
  },
  Profile: {
    screen: MyProfileScreen,
  },
});複製代碼

StackNavigator的代碼實現:
github.com/react-commu…

//摘錄StackNavigator.js
export default (
  routeConfigMap: NavigationRouteConfigMap,
  stackConfig: StackNavigatorConfig = {}
) => {
  ...
  const router = StackRouter(routeConfigMap, stackRouterConfig);

  const navigator = createNavigator(
    router,
    routeConfigMap,
    stackConfig,
    NavigatorTypes.STACK
  )((props: *) => (
    <CardStackTransitioner
      {...props}
      ...
    />
  ));

  return createNavigationContainer(navigator, stackConfig.containerOptions);
};複製代碼

使用StackRouter工廠方法生成router,傳入createNavigator

createNavigator的實現:
github.com/react-commu…

const createNavigator = (
  router: NavigationRouter<*, *, *>,
  routeConfigs: NavigationRouteConfigMap,
  navigatorConfig: any,
  navigatorType: NavigatorType
) => (View: NavigationNavigator<*, *, *, *>) => {
  class Navigator extends React.Component {
    props: NavigationNavigatorProps<*>;

    static router = router;
    ...
    render() {
      return <View {...this.props} router={router} />;
    }
  }

  return Navigator;
};複製代碼

createNavigator會返回一個函數,這個函數須要傳入View,這個View就是

(props: *) => (
    <CardStackTransitioner
      {...props}
      ...
    />
  )複製代碼

router做爲屬性傳入到VIew中,用來渲染界面,關於使用router渲染screen的分析下篇再說。CardStackTransitioner管理了CardStack的轉場動畫,手勢返回,createNavigator函數返回的函數傳入VIew參數後仍是返回一個Navigator(擁有router靜態屬性就把它當作Navigator)。

createNavigationContainer:的實現
github.com/react-commu…

export default function createNavigationContainer<T: *>(
  Component: ReactClass<NavigationNavigatorProps<T>>,
  containerOptions?: {}
) {
   ...
  class NavigationContainer extends React.Component<void, Props<T>, State> {
    state: State;
    props: Props<T>;

    //NavigationConatainer也是一個路由
    static router = Component.router;

    constructor(props: Props<T>) {
      super(props);
      ...
      this.state = {
        nav: this._isStateful()
          ? Component.router.getStateForAction(NavigationActions.init())
          : null,
      };
    }

    //當外部組件沒有傳入navigation屬性時,本身處理狀態
    _isStateful(): boolean {
      return !this.props.navigation;
    }
    ...

    //與Redex的dispatch同名,方便接入Redux,用意也相同,派發Action,經過getStateForAction改變State,從而刷新組件
    dispatch = (action: NavigationAction) => {
      const { state } = this;
      if (!this._isStateful()) {
        return false;
      }
      const nav = Component.router.getStateForAction(action, state.nav);
      if (nav && nav !== state.nav) {
        this.setState({ nav }, () =>
          ...
        );
        ...
      }
      ...
    };

    //關於android back的處理
    ....

    _navigation: ?NavigationScreenProp<NavigationRoute, NavigationAction>;

    render() {
      let navigation = this.props.navigation;
      //只有外部組件沒有傳入navigation時才本身建立navigation
      if (this._isStateful()) {
        //不存在navigation或者state.nav發生變化,從新獲取navigation
        if (!this._navigation || this._navigation.state !== this.state.nav) {
          this._navigation = addNavigationHelpers({
            dispatch: this.dispatch,
            state: this.state.nav,
          });
        }
        navigation = this._navigation;
      }
      //將navigtion做爲屬性傳給組件,這就是Container名稱的來意
      return <Component {...this.props} navigation={navigation} />;
    }
  }

  return NavigationContainer;
}複製代碼

因此若是外部傳入了navigation屬性,NavigationContainer就不作任何事情,就要直接渲染出Component並把屬性往下傳遞,若是沒有navigation屬性,則本身充當container,派發Action,管理State,刷新Navigator。

總結:
由routeConfig建立router,由router建立navigator,而後由navigator建立了createNavigationContainer,在NavigationContainer中使用isStateful判斷是否做爲container使用(本身充當container,派發Action,管理State,刷新Navigator)。

StackRouter:

StackRouter的代碼實現:
github.com/react-commu…
由上面的代碼分析能夠看出,經過StackRouter(routeConfigMap, stackRouterConfig)建立的router最終做爲了NavigationContainer的靜態屬性。那麼StackRouter(routeConfigMap, stackRouterConfig)建立的router是什麼樣的呢?

第一步:解析路由配置

const ModalStack = StackNavigator({
  Home: {
    screen: MyHomeScreen,
  },
  Profile: {
    screen: MyProfileScreen,
  },
});複製代碼

調用StackNavigator工廠方法的第一個參數就是routeConfigs

const childRouters = {};
  const routeNames = Object.keys(routeConfigs);
  console.log('開始解析路由配置...');
  routeNames.forEach((routeName: string) => {
    const screen = getScreenForRouteName(routeConfigs, routeName);
    //前面說過,經過router來判斷是否爲Navigator
    if (screen && screen.router) {
      // If it has a router it's a navigator. //這對後面路由的嵌套處理特別關鍵 childRouters[routeName] = screen.router; } else { // If it doesn't have router it's an ordinary React component. childRouters[routeName] = null; } console.log('路由配置解析結果:'); console.log(JSON.stringify(childRouters)) });複製代碼

由路由配置生成相應的childRoutes,注意這裏有三種狀態在後面會用到,分別是null, router, undefined,爲null則表明這個routeName配置過,可是不是子路由,router則表明是子路由,undefined表明沒有在路由配置中。

第二步:初始化路由棧

// Set up the initial state if needed
      if (!state) {
        //當state不存在時,初始化路由棧
        console.log('開始初始化初始路由爲' + initialRouteName + '的路由狀態...');
        let route = {};
        if (
          action.type === NavigationActions.NAVIGATE &&
          childRouters[action.routeName] !== undefined
        ) {
          //這是一種配置導航首頁的寫法,首頁有三種寫法,第一種是routeConfig的第一項,第二種是stackConfig中指定initialRouteName,第三種則是routeName與在父路由中註冊的routeName一致,則爲首頁。
          //這也是navigate是使用action.action會被調用的地方,後邊會提到
          return {
            index: 0,
            routes: [
              {
                ...action,
                type: undefined,
                key: `Init-${_getUuid()}`,
              },
            ],
          };
        }
        if (initialChildRouter) {
          //若是初始化路由爲子路由,則默認以initialRouteName爲首頁初始化子路由狀態
          console.log('初始化路由爲子路由時,獲取子路由的初始路由');
          route = initialChildRouter.getStateForAction(
            NavigationActions.navigate({
              routeName: initialRouteName,
              params: initialRouteParams,
            })
          );
          console.log(initialRouteName + '的初始路由爲:' + JSON.stringify(route))
        }
        //裝配params和route
        const params = (route.params ||
          action.params ||
          initialRouteParams) && {
          ...(route.params || {}),
          ...(action.params || {}),
          ...(initialRouteParams || {}),
        };
        route = {
          ...route, //將子路由嵌入進來
          routeName: initialRouteName,
          key: `Init-${_getUuid()}`,
          ...(params ? { params } : {}),
        };
        // eslint-disable-next-line no-param-reassign
        //裝配state
        state = {
          index: 0,
          routes: [route],
        };
        console.log('初始路由爲' + initialRouteName + '的路由狀態爲:' + JSON.stringify(state));
      }複製代碼

這裏舉個例子,你們慢慢領悟,路由配置爲:

const InvestScreen = TabRouter({
    WanjiaJX: {
        screen:WanjiaJX,
    },
    WanjiaY: {
        screen: WanjiaYing,
    },
}, {
    initialRouteName: 'WanjiaY',
})

const MainTabNavigator = TabNavigator({
    Home: {
        screen: HomeScreen,
    },
    Invest: {
        screen: InvestScreen,
    },
    Find: {
        screen: FindScreen,
    },
    My: {
        screen: MyScreen,
    },
})

const CardStackNavigator = StackNavigator({
    MainTab: {
        screen: MainTabNavigator,
    },
    transferDetail: {
        screen: TransferDetailScreen,
    }
});

const ModelStackNavigator = StackNavigator({
    mainStackNavigator: {
        screen: CardStackNavigator,
    },
    investSuccess: {
        screen: InvestSuccessScreen,
    },
    rechargeGetVcode: {
        screen: RechargeGetVcodeScreen,
    },
})

export default ModelStackNavigator複製代碼

打印結果爲:

開始解析路由配置...
StackRouter.js:42 {"MainTab":{},"transferDetail":{}}
StackRouter.js:55 路由配置解析結果:
StackRouter.js:56 {"MainTab":{},"transferDetail":null}
StackRouter.js:41 開始解析路由配置...
StackRouter.js:42 {"mainStackNavigator":{},"investSuccess":{},"rechargeGetVcode":{}}
StackRouter.js:55 路由配置解析結果:
StackRouter.js:56 {"mainStackNavigator":{},"investSuccess":null,"rechargeGetVcode":null}
StackRouter.js:105 開始初始化初始路由爲mainStackNavigator的路由狀態...
StackRouter.js:125 初始化路由爲子路由時,獲取子路由的初始路由
StackRouter.js:105 開始初始化初始路由爲MainTab的路由狀態...
StackRouter.js:125 初始化路由爲子路由時,獲取子路由的初始路由
StackRouter.js:132 MainTab的初始路由爲:{"routes":[{"key":"Home","routeName":"Home"},{"routes":[{"key":"WanjiaJX","routeName":"WanjiaJX"},{"key":"WanjiaY","routeName":"WanjiaY"}],"index":1,"key":"Invest","routeName":"Invest"},{"key":"Find","routeName":"Find"},{"key":"My","routeName":"My"}],"index":1}
StackRouter.js:152 初始路由爲MainTab的路由狀態爲:{"index":0,"routes":[{"routes":[{"key":"Home","routeName":"Home"},{"routes":[{"key":"WanjiaJX","routeName":"WanjiaJX"},{"key":"WanjiaY","routeName":"WanjiaY"}],"index":1,"key":"Invest","routeName":"Invest"},{"key":"Find","routeName":"Find"},{"key":"My","routeName":"My"}],"index":1,"routeName":"MainTab","key":"Init-id-1499828145378-0"}]}
StackRouter.js:132 mainStackNavigator的初始路由爲:{"index":0,"routes":[{"routes":[{"key":"Home","routeName":"Home"},{"routes":[{"key":"WanjiaJX","routeName":"WanjiaJX"},{"key":"WanjiaY","routeName":"WanjiaY"}],"index":1,"key":"Invest","routeName":"Invest"},{"key":"Find","routeName":"Find"},{"key":"My","routeName":"My"}],"index":1,"routeName":"MainTab","key":"Init-id-1499828145378-0"}]}
StackRouter.js:152 初始路由爲mainStackNavigator的路由狀態爲:{"index":0,"routes":[{"index":0,"routes":[{"routes":[{"key":"Home","routeName":"Home"},{"routes":[{"key":"WanjiaJX","routeName":"WanjiaJX"},{"key":"WanjiaY","routeName":"WanjiaY"}],"index":1,"key":"Invest","routeName":"Invest"},{"key":"Find","routeName":"Find"},{"key":"My","routeName":"My"}],"index":1,"routeName":"MainTab","key":"Init-id-1499828145378-0"}],"routeName":"mainStackNavigator","key":"Init-id-1499828145378-1"}]}複製代碼

用遞歸的方法從外到內獲取路由狀態,而後從內到外組裝路由狀態。

第三步:路由行爲
前面分析了StackRoute的初始化,下面將依次來分析navigate,setParams,reset,goBack的行爲。
react-navigation的Router的強大之處,也是難以理解之處就是路由的組合(composable)使用和相互嵌套。咱們常常會使用navigate來push,使用goBack來pop,使用reset來重置路由棧,使用setParams來重置params。

關於路由嵌套和navigate action

在上面路由配置的示例中,咱們提出兩個問題:
一、假如當前的頁面爲:mainStackNavigator->mainTab->Invest->WanjiaY,那在WanjiaYing的screen中,調用this.props.navigation.navigate('investSuccess'),或者調用this.props.navigation.navigate('transferDetail')會發生什麼?爲何會有這種效果?
二、假如當前的頁面爲:investSuccess,在InvestSuccessScreen中調用this.props.navigation.navigate('My')會有什麼效果?爲何會有這種效果?

若是能回答以上兩個問題,那對於導航的嵌套和組合使用就瞭解了。咱們先來分析代碼,而後再給出答案。

第一段代碼:

// Check if a child scene wants to handle the action as long as it is not a reset to the root stack
//只要不是對root stack的reset操做,都先檢查當前指定的子路由(使用key指定)或者activited狀態的子路由是否想處理改Action。
      if (action.type !== NavigationActions.RESET || action.key !== null) {
        //若是指定了key,則找到該key在state中的index,不然index爲-1
        const keyIndex = action.key
          ? StateUtils.indexOf(state, action.key)
          : -1;
        //當index < 0 時,則用activited狀態的index爲childIndex,不然用keyIndex做爲childIndex
        const childIndex = keyIndex >= 0 ? keyIndex : state.index;
        //經過childIndex找到childRoute
        const childRoute = state.routes[childIndex];
        //經過childRoute的routeNam在childRouter中查詢是否爲子路由
        const childRouter = childRouters[childRoute.routeName];
        if (childRouter) {
          //若是存在子路由,則讓子路由去處理這個Action,而且傳入對應的childRoute
          const route = childRouter.getStateForAction(action, childRoute);
          //若是route爲null則返回當前state
          if (route === null) {
            return state;
          }
          //若是route不等於以前的childRoute,也就是route不等於preRoute
          if (route && route !== childRoute) {
            //在state的對應的key中作replace操做
            return StateUtils.replaceAt(state, childRoute.key, route);
          }
        }
      }複製代碼

在上面代碼中要好好理解兩個字段,一個是childRouter,一個是childRoute,它們在字面上只有一個r的區別,可是實際的用處是相差甚遠的,childRouter是一個Router,它就像StackRouter或者TabRouter同樣,擁有getStateForAction這些方法,childRoute是一個Route,它是存在於State中的,它的結構相似於:

{
  key: ***,
  routeName: mainTab,
  ...
}複製代碼

所以,在State中經過key找到Route,經過Route中的routeName在childRouter中找到是否有響應的router,來判斷它是否爲子路由,這個邏輯就說的很通了。
因此這段代碼的意思能夠通俗的描述爲:在非root stack的reset的action中,指定(經過key或者activited route)一個childRouter來處理action。

第二段代碼:

// Handle explicit push navigation action
//處理確切的navigate action,所謂確切,就是說在routeConfig中有*直接*註冊過
      if (
        action.type === NavigationActions.NAVIGATE &&
        childRouters[action.routeName] !== undefined
      ) {
        const childRouter = childRouters[action.routeName];
        let route;
        if (childRouter) {
          //若是navigate的是一個子路由,而且存在子action(action.action)則讓子路由執行這個子action
          //若是沒有子action則使用init爲action
          const childAction =
            action.action || NavigationActions.init({ params: action.params });
          route = {
            params: action.params,
            ...childRouter.getStateForAction(childAction), //注意,getStateForAction的第二個參數沒有傳,證實它是用來初始化狀態的。
            key: _getUuid(),
            routeName: action.routeName,
          };
        } else {
          //沒有子路由則直接構建route
          route = {
            params: action.params,
            key: _getUuid(),
            routeName: action.routeName,
          };
        }
        //直接push route
        return StateUtils.push(state, route);
      }複製代碼

這段代碼就比較簡單了,在當前(不會去遞歸到子路由)路由配置中找是否有註冊過routeName的screen,若是有註冊,則分兩種狀況,第一種,這個screen就是一個普通screen,直接構建route便可,第二種是,這個screen是一個navigator,初始化子路由狀態(使用action.action或者init)而後組裝route。最後都要將route push到state中去。

這裏有個問題,在...childRouter.getStateForAction(childAction)這句代碼中,若是childRouter爲StackRouter,則會調用到第二步:初始化路由棧中的下面這段代碼來:

if (
          action.type === NavigationActions.NAVIGATE &&
          childRouters[action.routeName] !== undefined
        ) {
          //這是一種配置導航首頁的寫法,首頁有三種寫法,第一種是routeConfig的第一項,第二種是stackConfig中指定initialRouteName,第三種則是routeName與在父路由中註冊的routeName一致,則爲首頁。
          return {
            index: 0,
            routes: [
              {
                ...action,
                type: undefined,
                key: `Init-${_getUuid()}`,
              },
            ],
          };
        }複製代碼

在這段代碼中,若是action.routeName的childRouter依然是一個子路由,即childRouters[action.routeName] !== null則會報錯,由於CardStack要渲染的screen爲一個navigator,可是state中卻沒有相對應的routes和index。報錯信息:
Expect nav state to have routes and index, {"routeName": "MainTab", "key": "Init-id-123322334-3"}
改爲以下便可:

if (
          action.type === NavigationActions.NAVIGATE &&
          childRouters[action.routeName] !== undefined
        ) {
          if(childRouters[action.routeName]) {
              const childRouter = childRouters[action.routeName];
              state = {
                  index: 0,
                  routes: [
                      {
                          ...action,
                          ...childRouter.getStateForAction(action),
                          type: undefined,
                          key: `Init-${_getUuid()}`,
                      },
                  ],
              };
              console.log('返回狀態:' + JSON.stringify(state));
              return state;
          }
          state = {
            index: 0,
            routes: [
              {
                ...action,
                type: undefined,
                key: `Init-${_getUuid()}`,
              },
            ],
          };
          console.log('返回狀態:' + JSON.stringify(state));
          return state;
        }複製代碼

第三段代碼:

//當指定的子路由沒有處理,路由配置中沒有配置響應的routeName時,遍歷*全部*子路由,一旦有子路由願意處理該action,則將處理結果push返回。
      if (action.type === NavigationActions.NAVIGATE) {
        const childRouterNames = Object.keys(childRouters);
        for (let i = 0; i < childRouterNames.length; i++) {
          const childRouterName = childRouterNames[i];
          const childRouter = childRouters[childRouterName];
          if (childRouter) {
            //遍歷子路由,從初始狀態開始,處理Action
            const initChildRoute = childRouter.getStateForAction(
              NavigationActions.init()
            );
            //檢查子路由是否想處理action
            const navigatedChildRoute = childRouter.getStateForAction(
              action,
              initChildRoute
            );
            let routeToPush = null;
            if (navigatedChildRoute === null) {
              // Push the route if the router has 'handled' the action and returned null
              //若是子路由處理了這個action,而且返回null,則push子路由的初始狀態
              routeToPush = initChildRoute;
            } else if (navigatedChildRoute !== initChildRoute) {
              //若是子路由處理了這個action,並改變了初始狀態,則push這個新的路由狀態
              routeToPush = navigatedChildRoute;
            }
            if (routeToPush) {
              return StateUtils.push(state, {
                ...routeToPush,
                key: _getUuid(),
                routeName: childRouterName,
              });
            }
          }
        }
      }複製代碼

總結:
以上分析了路由是如何管理子路由的,在處理action時會先判斷該action不是root stack的reset操做時(action.type !== NavigationActions.RESET || action.key !== null),找到指定的router(找到action.key對應的router,當action.key無效時找到activited router)去處理該action,若是指定的router處理了這個action(返回null或者route!==childRoute)則在state中替換對應的route。
若是指定(經過key或者activited router)的router不處理action,則判斷action.routeName有沒有在routeConfig中註冊過,對於這種直接註冊的,就直接push就好。
若是action.routeName沒有被註冊過,則遍歷全部子路由去嘗試處理action,一旦有子路由去處理了,則直接push這個處理結果。

因此,你能回答上面兩個問題了嗎?

setParams:
代碼:

if (action.type === NavigationActions.SET_PARAMS) {
        //經過action.key在state中找到對應的route
        const lastRoute = state.routes.find(
          /* $FlowFixMe */
          (route: *) => route.key === action.key
        );
        if (lastRoute) {
          //若是route存在,將參數合併
          const params = {
            ...lastRoute.params,
            ...action.params,
          };
          //作這一步是爲了改變route的引用
          const routes = [...state.routes];
          routes[state.routes.indexOf(lastRoute)] = {
            ...lastRoute,
            params,
          };
          //返回一個全新的state
          return {
            ...state,
            routes,
          };
        }
      }複製代碼

用法:

import { NavigationActions } from 'react-navigation'

const setParamsAction = NavigationActions.setParams({
  params: { title: 'Hello' },
  key: 'screen-123',
})
this.props.navigation.dispatch(setParamsAction)複製代碼

reset:
代碼:

if (action.type === NavigationActions.RESET) {
        const resetAction: NavigationResetAction = action;

        return {
          ...state,
          routes: resetAction.actions.map( //遍歷action.actions
            (childAction: NavigationNavigateAction) => {
              const router = childRouters[childAction.routeName];
              if (router) {
                //當childAction.routerName爲子路由時,獲取子路由的初始狀態
                return {
                  ...childAction,
                  ...router.getStateForAction(childAction), //這裏沒傳第二個參數,是去獲取初始狀態
                  routeName: childAction.routeName,
                  key: _getUuid(),
                };
              }
              //直接建立route
              const route = {
                ...childAction,
                key: _getUuid(),
              };
              delete route.type;
              return route;
            }
          ),
          index: action.index,
        };
      }複製代碼

用法:

import { NavigationActions } from 'react-navigation'

const resetAction = NavigationActions.reset({
  index: 0,  //肯定route的index
  actions: [ //肯定routes
    NavigationActions.navigate({ routeName: 'Profile'})
  ]
})
this.props.navigation.dispatch(resetAction)複製代碼

goBack:
代碼:

if (action.type === NavigationActions.BACK) {
        //當前要pop的index
        let backRouteIndex = null;
        if (action.key) {
          //經過key找到route,經過route找到index
          const backRoute = state.routes.find(
            /* $FlowFixMe */
            (route: *) => route.key === action.key
          );
          /* $FlowFixMe */
          //賦值當前要pop的index
          backRouteIndex = state.routes.indexOf(backRoute);
        }
        if (backRouteIndex == null) {
          //當index不存在時,直接pop最上面的route
          return StateUtils.pop(state);
        }
        if (backRouteIndex > 0) { 
          //pop route到index爲backRouteIndex - 1
          return {
            ...state,
            routes: state.routes.slice(0, backRouteIndex),
            index: backRouteIndex - 1, 
          };
        }
      }
      return state;
    },複製代碼

用法:

import { NavigationActions } from 'react-navigation'

const backAction = NavigationActions.back({
  key: 'Profile'
})
this.props.navigation.dispatch(backAction)複製代碼

注意:setParams、reset、goBack都是能夠經過key來使用的,能夠自動在一個conatiner navigtor中指定router來處理這些action。在StackRouter中key是使用_getUuid直接生成的,能夠用過this.props.navigation.state.key獲取到。

總結

項目中使用了redux + react-navigation,仍是以爲很好用的,可是剛開始學習時感受與之前使用過的navigation在思想上有種種不一樣,不少時候想自定義或者修改時經常找不到地方,好比防止push一樣的screen,指定routeName來back等,可是多看文檔和源碼後,發現它的自由度是很是高的,能夠重寫或攔截router,自定義NavigatorView,靈活的配置Transition等,配合redux也是很是好用的。值得推。
其實這些官方文檔都有所描述,只是以前看的雲裏霧裏,如今終有所理解,但願對和我同樣在使用react-navigation時有疑問的同窗有所幫助。

歡迎關注個人簡書主頁:www.jianshu.com/u/b92ab7b3a… 文章同步更新^_^

相關文章
相關標籤/搜索