阅读 1840

react-navigation路由篇之StackRouter

react-navigation的基础认识:

react-navigation是官方正在推的导航,但是没有放入到react-native包中,而是单独开了一个库,它的核心概念是Router,Router的概念图如下所示:

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

最上方的文字:
上面这个图清晰的表达出了Router能干什么,最重要的一句话是:Router经常组合使用,并且能代理子路由,这句话的意思我待会分析源码来深入了解。

上图的右部分:
由Action而引起State的变化,是不是很像Redux?后面我会写篇文章专门写如何配合Redux自定义行为。在不配合Redux使用时,它自己内部其实也通过createNavigationContainer(后边源码分析会说到)来作为主容器维护这类似Redux形式的数据流。用户在使用App的过程中触发导航Action,例如StackRouter的Navigate,goBack等,这些Action被Dispatch出去后会被router.getStateForAction(action, lastState)处理,getStateForAction通过Action和之前的State获得新的State。这就是一个完整的数据流过程。

上图的左部分:
除了在App运行过程中用户主动触发goBack,navigate这些Action外,当App不在运行时,也可以通过推送,通知等唤醒App来派发Action,router.getActionForPathAndParams通过path和params可以取得Action,然后就可以走图右部分的流程了。

由上面的分析可以知道,router是用来管理导航状态,它的原理就是通过派发Action来获得新的State,从而维护和管理导航的状态,导航的State的结构如下:

//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。

上面对Router有了大概的了解,注意是Router,因为后面会说到route,经常会混淆。Router是用来管理导航的State,是导航的Core,StackNavigator的navigate,back操作的逻辑都是由StackRouter处理的,TabNavigator的navigate,back操作的逻辑都是由TabRouter处理的。用Router的State来渲染当前Screen,管理手势和动画的任务,就要配合Navigation View和Transitioner了。
Navigation View
Transitioner


说在前边的话

这篇文章是分析导航路由的实现原理,而不是讲解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

在上面路由配置的示例中,我们提出两个问题:
1、假如当前的页面为:mainStackNavigator->mainTab->Invest->WanjiaY,那在WanjiaYing的screen中,调用this.props.navigation.navigate('investSuccess'),或者调用this.props.navigation.navigate('transferDetail')会发生什么?为什么会有这种效果?
2、假如当前的页面为: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… 文章同步更新^_^

关注下面的标签,发现更多相似文章
评论