React-Navigation实现动态Tab路由

有一个需求:在用户未登录和已经登陆的情况下,需要渲染不同的底部导航菜单,而该导航栏其实是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
// tab nav配置
const RouteConfigs: TabOptions = {
Home: { screen: Home },
Purchase: { screen: Purchase },
Brand: { screen: Brand },
Sell: { screen: Sell },
Management: { screen: Management }
}
// 创建底部tab路由
const BottomTabRoutes = createBottomTabNavigator(RouteConfigs, {
...TabNavigatorConfig
})
// 接入到App路由中
const AppNavigator = createStackNavigator(
{
TabRouter: { screen: BottomTabRoutes },
...pageRoutes
}
)

createBottomTabNavigator接收RouteConfigs作为参数,返回一个类型为NavigationContainer的值:

1
2
// NavigationContainer 类型定义
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 = [....]
// 对`NavConfig`的一系列的处理
return createBottomTabNavigator(NavConfig, { ...otherConfig })
}
render() {
const Tabs = this._genNav()
return <Tabs />
}
}
const AppNavigator = createStackNavigator(
{
TabRouter: { screen: DynamicTabNavigator },
...otherRouter
}
)

这种方式在该issue被证实是可行的,但是在React Navigation3.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() {
// tslint:disable-next-line: no-unused-expression
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无法支持动态路由的问题,以上给出了两种方案,能够在一定程度解决。

  1. 第一种方案按需挂载路由,可以算是“真”动态路由;
  2. 第二种方案从可定制的tabBarComponent入手,不改变路由配置,而是在渲染层进行控制,条件渲染BottomTabBar
分享到 评论