React Navigation 5.x(二)嵌套路由动态配置标题栏及自定义tabbar点击事件

接上篇React Navigation 5.x(一)常用知识点梳理

我正在开发的App,路由结构是很典型的根堆栈导航器的首屏嵌套一个tab导航器,其余不显示选项卡的Screen都放在这屏的后面。
现在我有这两个需求:1、在嵌套路由中动态配置顶部标题栏(tab导航器嵌套在根部stack导航器的首屏);2、监听tab点击事件,触发时将对应screen重置到初始状态(数据)。

我的基础路由结构如下:

1.我把路由单独拎出来放在/src/router/index.js里,再引入App.js

// App.js
import 'react-native-gesture-handler';
import React from 'react';
import Routes from '@router'; // 路径为插件定义过的别名,实际是"/src/router/index.js"

const App = () => {
  return ;
}

export default App;

2./src/router/index.js内容:

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Tabbar from '@components/core/Tabbar'
import GuideDetail from '@components/detail/Guide'
import MuseumDetail from '@components/detail/Museum'

const RootStack = createStackNavigator();

const Routes = () => {
  return (
    
      
        
        
        
      
    
  )
}

export default Routes

3.为了代码可读性和后期维护的便利,我把tab导航器也放到另一个js里而不是直接写在index.js里。这已经是我加完所有配置的tabNavigator组件,不是最基础的结构。关于tabNavigator样式和其他参数配置,不清楚可以参考bottom-tab-navigator
/src/components/core/Tabbar.js

import React from 'react';
import { Icon } from '@ant-design/react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { StyleSheet } from 'react-native';

import Guide from '@components/home/Guide'
import Museum from '@components/home/Museum'
import Community from '@components/home/Community'
import Archive from '@components/home/Archive'
import User from '@components/home/SearchGuide'

const styles = StyleSheet.create({
  tabBarStyle: { 
    position: 'absolute'
  },
  tabStyle: {
    paddingTop: 5,
    paddingBottom:5
  },
  labelStyle: {
    fontSize: 12 
  },
})

const Tab = createBottomTabNavigator();

export default function MyTabs() {
  return (
     ({
        tabBarIcon: ({ color, size }) => { // 自定义tabbar的图标
          const icons = {
            Guide: 'book',
            Museum: 'bank',
            Community: 'comment',
            Archive: 'picture',
            User: 'user'
          };
          return (
            
          );
        },
      })}
    >
      
      
      
      
      
    
  );
}

嵌套路由之路由分组

上一篇讲过官方建议路由嵌套尽量不要超过两层,但这样难免会导致放在同一级的screen组件过多,层次条理不够清晰。于是我们可以这样做:官方最佳方案
即是把路由组件按功能分组,再通过map添加到Navigator里。

const homeScreen = {
  Home: Tabbar,
}
const detailScreens = { // 详情页组
  GuideDetail,
  MuseumDetail
}

const Routes = () => {
  return (
    
       (setHeaderTitle(route))}
        headerMode="screen" // 如果要自定义配置header,必须设置这个为screen
      >
        {Object.entries({
          // 首页组
          ...homeScreen,
          // 详情页组
          ...detailScreens}).map(([name, component]) => (
          
        ))}
      
    
  )
}

添加动态配置顶部标题栏功能

首先看下这是我用来动态设置屏幕选项(screenOptions)的方法:

import { NavigationContainer, getFocusedRouteNameFromRoute } from '@react-navigation/native';

const setHeaderTitle = (route) => {
  let title = '动森之家' // 默认标题
  // getFocusedRouteNameFromRoute可以获取路由的name
  const routeName = getFocusedRouteNameFromRoute(route) ?? 'Guide';
  if(routeName){ // 再根据路由名来设置title
    switch(routeName) {
      case 'Museum':
        title = '博物馆图鉴'
        break
      case 'Community':
        title = '社区'
        break
      case 'Archive':
        title = '图鉴'
        break
      case 'User':
        title = '我的'    
        break
    }
  }
  if(route.params && route.params.title) { // 如果路由参数里有title属性,则该页标题以此为准
    title = route.params.title
  }
  const screenOptions={
    headerTitleAlign: 'center',
    headerTintColor: 'white',
    headerStyle: { backgroundColor: '#81C784', height: 60 },
    // header: myHeader, // (1)返回React元素的函数,内容是自定义标题栏组件
    // headerTitle: (props) => , // (2)返回React元素的函数,内容是自定义的标题文字组件
    headerTitle: title, // (3)直接设置字符串,最简洁的方法
  }
  return screenOptions
}

看到上面screenOptions配置中,我们主要通过headerTitle来实现我们的需求,有三种方法:
(1) 自定义标题栏
这是官方的例子,一般对标题栏样式和精细度要求比较高才会用到,我这边没有实际使用。注意:如果要使用这个配置必须要在Navigator设置headerMode:'screen'

header: ({ scene, previous, navigation }) => {
  const { options } = scene.descriptor;
  const title =
    options.headerTitle !== undefined
      ? options.headerTitle
      : options.title !== undefined
      ? options.title
      : scene.route.name;

  return (
     : undefined
      }
      style={options.headerStyle}
    />
  );
};

(2) 自定义标题文字组件,替代原来的中间文字组件。当需求不仅仅是更改标题的文本和样式-比如:渲染图像来代替标题,或将标题变成按钮。就可以这样子来使用。

function LogoTitle(props) { // 如果需要渲染图像作为标题
  return (
    
  );
}

function MyTitle(props) { // 自定义标题文字组件,替代原来的中间文字组件,这里的写法效果和设置headerTitle: title是一样一样的。
  return (
    {props.props.title}
  );
}

// 配置中使用
  }}

(3) 没有什么额外要求的话,直接设置headerTitle为方法得到的title字符串即可。headerTitle: title

自定义选项卡导航器(tab Navigator)的tabPress事件:

https://reactnavigation.org/docs/bottom-tab-navigator/#tabpress
当tabbar(选项卡栏)中当前Screen的tab按钮被按下时时触发此事件。这个事件除了让对应的tab按钮获得焦点,默认还会做这几件事:
如果这个tab对应的screen渲染的是一个滚动视图,您可以使用useScrollToTop Hook使其滚动到顶部;
如果tab对应的屏幕是stack navigator,则在堆栈上执行popToTop操作,即导航到该navigator的初始路由屏幕。

如果我们在点击tab按钮时有做些其他事情的需求,可以阻止这些默认事件。如下:

// tabbar的其中一个screen组件
const listRef = useRef(null)

const GuideScreen = ({ navigation, route }) => {
  useEffect(() => {
    const unsubscribe = navigation.addListener('tabPress', (e) => {
      // 阻止默认行为
      e.preventDefault();
      // 手动跳转到这个GuideScreen页面
      navigation.jumpTo('GuideScreen',{msg:'我是攻略页'})
      getBanner() // 再重新获取下焦点图数据
      listRef.current.onRefresh() // 让另一个子组件list的数据刷新
      // ...
  });
  return unsubscribe;
  }, [navigation]);

  return (
    <>
      
      你好呀,{route.params.msg}
      
    
  )
}

这边省略了其他无关的代码,比如请求方法、子组件啊,这边主要讲路由就不赘述,下次有空再展开写篇FlatList列表排序筛选功能结合Hook使用的文。

你可能感兴趣的:(React Navigation 5.x(二)嵌套路由动态配置标题栏及自定义tabbar点击事件)