有一个需求:在用户未登录和已经登陆的情况下,需要渲染不同的底部导航菜单,而该导航栏其实是react-navigation-tabs
的实例,并且不支持动态导航。
这个是一个很常见的需求,在这个issue下面有很多讨论,针对此需求,也提供了一系列解决方案。
动态导航
我们使用createBottomTabNavigator(RouteConfigs, TabNavigatorConfig)
来创建tab
导航,其中RouteConfigs
接受一个导航名称和路由的映射对象,一般情况下,RouteConfigs
是确定的,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const RouteConfigs: TabOptions = { Home: { screen: Home }, Purchase: { screen: Purchase }, Brand: { screen: Brand }, Sell: { screen: Sell }, Management: { screen: Management } }
const BottomTabRoutes = createBottomTabNavigator(RouteConfigs, { ...TabNavigatorConfig })
const AppNavigator = createStackNavigator( { TabRouter: { screen: BottomTabRoutes }, ...pageRoutes } )
|
createBottomTabNavigator
接收RouteConfigs
作为参数,返回一个类型为NavigationContainer
的值:
1 2
| interface NavigationContainer extends React.ComponentClass<NavigationContainerProps NavigationNavigatorProps<any>> {...}
|
通过查看NavigationContainer
的定义,可以发现其是一个React
组件。所以第一种方式就是自定义一个组件,在该组件中返回NavigationContainer
实例即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class DynamicTabNavigater extends Compoent { _genNav() { let NavConfig = [....] return createBottomTabNavigator(NavConfig, { ...otherConfig }) } render() { const Tabs = this._genNav() return <Tabs /> } } const AppNavigator = createStackNavigator( { TabRouter: { screen: DynamicTabNavigator }, ...otherRouter } )
|
这种方式在该issue被证实是可行的,但是在React Navigation
3.x版本中报错,显示缺少AppContainer
,所以还需要使用createAppContainer
创建一个容器。
1 2 3 4 5
| ... render() { const Tabs = createAppContainer(this._genNav()) return <Tabs /> }
|
这样动态路由就实现了,并且能够在绝大部分情况下使用正常,由于使用createAppContainer
创建了一个容器,如果该容器并无法包含所有路由,那么还需要的AppContainer
,此时就会导航异常。
实现二
继续关注createBottomTabNavigator(RouteConfigs, TabNavigatorConfig)
方法,第二个参数TabNavigatorConfig
包含一个属性tabBarComponent?: React.ReactType
,该属性用于设置tabBar
如何显示,该属性设置为一个组件。
所以机会来了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import * as React from 'react' import { BottomTabBar } from 'react-navigation-tabs' import { DeviceEventEmitter } from 'react-native'
interface NavigatorState { showBrandPage: boolean }
class DynamicTabNavigator extends React.Component<any, NavigatorState> { state: NavigatorState = { showBrandPage: true } subscribe: any componentDidMount() { this.subscribe = DeviceEventEmitter.addListener( 'showBrand', (data: boolean) => { this.setState({ showBrandPage: data }) } ) } componentWillUnmount() { this.subscribe && this.subscribe.remove() } _tabNav = () => { const { routes, index } = this.props.navigation.state const finalRoutes = [...routes] const { showBrandPage } = this.state return { state: { index: finalRoutes.findIndex(route => currentRoute.key === route.key), routes: finalRoutes } } } render() { const { navigation, ...restProps } = this.props const tabNavConfig = this._tabNav() return <BottomTabBar {...restProps} navigation={tabNavConfig} /> } }
export default DynamicTabNavigator
const BottomTabRoutes = createBottomTabNavigator(tabNav, { tabBarComponent: DynamicTabNavigator })
|
通过eventListener
的方式接收路由变更信号,最终渲染BottomTabBar
时使用修改过后的配置即可。
这种方式其实是一种障眼法,我们需要在配置静态Tab路由RouteConfigs
时配置所有的Tab路由,在DynamicTabNavigator
中通过props
注入的navigation.state.routes
拿到配置的静态路由,并经过一系列的处理最终得到渲染到BottomTabBar
中的路由。需要注意的是,如果最终的路由相比静态路由有调整,那么需要更新index
,否则点击路由跳转时会出现错误。
总结
针对React Navigation
无法支持动态路由的问题,以上给出了两种方案,能够在一定程度解决。
- 第一种方案按需挂载路由,可以算是“真”动态路由;
- 第二种方案从可定制的
tabBarComponent
入手,不改变路由配置,而是在渲染层进行控制,条件渲染BottomTabBar
。