嵌套路由配置及生命周期

924 阅读4分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

options中的属性:

  • 跨页面的公共设置

    <Stack.Navigator
          screenOptions={{
            headerStyle: {
              backgroundColor: '#f4511e',
            },
            headerTintColor: '#fff',
            headerTitleStyle: {
              fontWeight: 'bold',
            },
          }}
        >
          <Stack.Screen
            name="Home"
            component={HomeScreen}
            options={{ title: '首页' }}
          />
        </Stack.Navigator>
    
  • title:标题

    <Stack.Navigator>
          <Stack.Screen
            name="Home"
            component={HomeScreen}
            options={{ title: '首页' }}
          />
        </Stack.Navigator>
    
  • 使用参数传递过来的标题

    传入选项函数的参数是具有以下属性的对象:

    navigation - 页面的导航

    route - 页面的路由;

    <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={ProfileScreen}
          options={({ route }) => ({ title: route.params.name })}
         />
    </Stack.Navigator>
    
  • setOptions() 设置tabbar组件上的属性

    navigation.setOptions({ title: 'Updated!' })
    
  • 头部标题栏

    • headerStyle 设置头部标题栏样式

      headerStyle: {
        	backgroundColor: 'red',
      },
      
    • headerTinColor:设置头部标题栏字体颜色

    • headerTitleStyle:设置头部标题样式

      headerTitleStyle: {
        	fontWeight: 'bold',
          fontSize: 30,
      },
      
  • 自定义头部标题栏

    • headerTitle 自定义标题文字,可以传入一个组件
    • headerBackTitleVisible 是否显示返回按钮的文字
    • headerBackTitle 设置返回按钮的文字
    • headerRight 设置头部标题右边样式,可传入一个组件
    • headerBackImage 自定义返回图标,返回一个图片组件

嵌套导航器意味着在另一个导航器的屏幕内呈现导航器;一般主页面都会有tabbar栏,如下:

import React, { Component } from 'react';

import { NavigationContainer } from '@react-navigation/native';
import {
    createStackNavigator,
    HeaderBackButton,
    StackNavigationProp,
} from '@react-navigation/stack';
import Found from '../pages/found/Found';
import List from '../pages/list/List';
import Me from '../pages/me/Me';
import BottomTabs from './ButtomTab';
import Detail from '../pages/detail/Detail';
import Login from '../pages/login/Login';
import Search from '../pages/search/Search';
import Play from '../pages/play/Play';
import MVPlay from '../pages/mv-play/MVPlay';
import CustomTitleBar from '../pages/custom-title-bar/CustomTitleBar';
import CustomHeader from '../components/CustomHeader';
import { Button } from '@ant-design/react-native';
import { Alert, Image } from 'react-native';

export type RootStackParamList = {
    BottomTabs: undefined;
    List: undefined;
    Found: undefined;
    Me: undefined;
    Login: undefined;
    Detail: {
        topId: number;
    };
    Search: undefined;
    Play: {
        id: number;
    };
    MVPlay: {
        vid: string;
    };
    CustomTitleBar: undefined;
};

export type RootStackNavigation = StackNavigationProp<RootStackParamList>;

const Stack = createStackNavigator<RootStackParamList>();
export default class Navigator extends Component {
    render() {
        return (
            <NavigationContainer>
                <Stack.Navigator>
                    <Stack.Screen
                        name="BottomTabs"
                        component={BottomTabs}
                        options={{
                            headerTitle: '首页',
                            headerStyle: {
                                backgroundColor: 'red',
                            },
                            headerTintColor: '#fff',
                            headerTitleStyle: {
                                fontWeight: 'bold',
                                fontSize: 30,
                            },
                        }}
                    />
                    <Stack.Screen
                        name="List"
                        component={List}
                        options={{ headerTitle: '榜单' }}
                    />
                    <Stack.Screen
                        name="Found"
                        component={Found}
                        options={{ headerTitle: '发现' }}
                    />
                    <Stack.Screen
                        name="Me"
                        component={Me}
                        options={{ headerTitle: '我的' }}
                    />
                    <Stack.Screen
                        name="Login"
                        component={Login}
                        options={{ headerTitle: '登录' }}
                    />
                    <Stack.Screen
                        name="Detail"
                        component={Detail}
                        options={{ headerTitle: '歌单' }}
                    />
                    <Stack.Screen
                        name="Search"
                        component={Search}
                        options={{ headerTitle: '搜索' }}
                    />
                    <Stack.Screen
                        name="Play"
                        component={Play}
                        options={{ headerTitle: '歌曲' }}
                    />
                    <Stack.Screen
                        name="MVPlay"
                        component={MVPlay}
                        options={{ headerTitle: 'mv' }}
                    />
                    <Stack.Screen
                        name="CustomTitleBar"
                        component={CustomTitleBar}
                        options={{
                            headerTitle: (props) => <CustomHeader {...props} />,
                            // headerBackTitleVisible: false,
                            headerBackTitle: '1234',
                            headerRight: () => (
                                <Button onPress={() => Alert.alert('right')}>
                                    right
                                </Button>
                            ),
                            headerBackImage: () => (
                                <Image
                                    source={{
                                        uri: 'https://qpic.y.qq.com/music_cover/DhpicvGxCZozibtVUC0Q03Oia0h9DnKUNHPdPL3oD2tqUJiaYJUv1jvlEXbPvCCy4Vql/300?n=1',
                                    }}
                                    style={[
                                        {
                                            width: 60,
                                            height: 60,
                                            marginRight: 20,
                                        },
                                    ]}
                                />
                            ),
                        }}
                    />
                </Stack.Navigator>
            </NavigationContainer>
        );
    }
}

完整代码如下:

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import React, { Component } from 'react';
import {
    getFocusedRouteNameFromRoute,
    RouteProp,
    TabNavigationState,
} from '@react-navigation/native';
import { RootStackNavigation, RootStackParamList } from './index';
import Home from '../pages/home/Home';
import List from '../pages/list/List';
import Found from '../pages/found/Found';
import Me from '../pages/me/Me';

export type BottomTabParamList = {
    Home: undefined;
    List: undefined;
    Found: undefined;
    Me: undefined;
};

const Tab = createBottomTabNavigator<BottomTabParamList>();

type Route = RouteProp<RootStackParamList, 'BottomTabs'> & {
    state?: TabNavigationState<BottomTabParamList>;
};

interface IProps {
    navigation: RootStackNavigation;
    route: Route;
}

export default class BottomTabs extends Component<IProps> {
    // 获取每个页面的标题
    getHeaderTitle(route: Route): string {
        const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';

        switch (routeName) {
            case 'Home':
                return '首页';
            case 'List':
                return '榜单';
            case 'Found':
                return '发现';
            case 'Me':
                return '我的';
            default:
                return '首页';
        }
    }

    componentDidUpdate() {
        const { navigation, route } = this.props;
        navigation.setOptions({
            headerTitle: this.getHeaderTitle(route),
        });
    }
  
    render() {
        return (
            <Tab.Navigator
                tabBarOptions={{
                    activeTintColor: '#f86442', // 修改tabbar激活颜色
                }}>
                <Tab.Screen
                    name="Home"
                    component={Home}
                    options={{ tabBarLabel: '首页' }}
                />
                <Tab.Screen
                    name="List"
                    component={List}
                    options={{ tabBarLabel: '榜单' }}
                />
                <Tab.Screen
                    name="Found"
                    component={Found}
                    options={{ tabBarLabel: '发现' }}
                />
                <Tab.Screen
                    name="Me"
                    component={Me}
                    options={{ tabBarLabel: '我的' }}
                />
            </Tab.Navigator>
        );
    }
}

其中首页、榜单、发现、我的,这几个页面都有个底部tabbar;这就是一个嵌套路由,首次加载的就是这个有tabbar的组件,点击tabbar的按钮可以切换。

React Navigation的生命周期

我们使用了一个堆栈导航器,它有两个屏幕(Home和Detail),并学习了如何使用navigation.navigate('RouteName') 在路由之间导航。

在这种情况下,一个重要的问题是:当我们离开它时,或者当我们回到它时,Home会发生什么? 路由如何发现用户是离开它还是返回它?

如果你要从一个web背景来进行反应式导航,你可以假设当用户从路由a导航到路由B时,a将卸载(它的componentWillUnmount 被调用),而a将在用户返回到它时再次加载。虽然这些React生命周期方法仍然有效,并且在React -navigation中使用,但它们的用法不同于web。这是由更复杂的移动导航需求驱动的。

比如一个带有屏幕a和b的堆栈导航器。导航到a后,调用它的componentDidMount。当push B时,它的componentDidMount也会被调用,但是A仍然挂载在堆栈上,因此它的componentWillUnmount不会被调用。

当从B返回到A时,B的componentWillUnmount被调用,但A的componentDidMount没有被调用,因为A一直保持挂载;类似的结果也可以结合嵌套导航。考虑一个有两个标签的标签导航器,其中每个标签都是堆栈导航器:

import * as React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function SettingsScreen({ navigation }) {
    return (
        <View
            style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Text>Settings Screen</Text>
            <Button
                title="Go to Profile"
                onPress={() => navigation.navigate('Profile')}
            />
        </View>
    );
}

function ProfileScreen({ navigation }) {
    return (
        <View
            style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Text>Profile Screen</Text>
            <Button
                title="Go to Settings"
                onPress={() => navigation.navigate('Settings')}
            />
        </View>
    );
}

function HomeScreen({ navigation }) {
    return (
        <View
            style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Text>Home Screen</Text>
            <Button
                title="Go to Details"
                onPress={() => navigation.navigate('Details')}
            />
        </View>
    );
}

function DetailsScreen({ navigation }) {
    return (
        <View
            style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Text>Details Screen</Text>
            <Button
                title="Go to Details... again"
                onPress={() => navigation.push('Details')}
            />
        </View>
    );
}


const Tab = createBottomTabNavigator();
const SettingsStack = createStackNavigator();
const HomeStack = createStackNavigator();

export default function App() {
    return (
        <NavigationContainer>
            <Tab.Navigator>
                <Tab.Screen name="First">
                    {() => (
                        <SettingsStack.Navigator>
                            <SettingsStack.Screen
                                name="Settings"
                                component={SettingsScreen}
                            />
                            <SettingsStack.Screen
                                name="Profile"
                                component={ProfileScreen}
                            />
                        </SettingsStack.Navigator>
                    )}
                </Tab.Screen>
                <Tab.Screen name="Second">
                    {() => (
                        <HomeStack.Navigator>
                            <HomeStack.Screen
                                name="Home"
                                component={HomeScreen}
                            />
                            <HomeStack.Screen
                                name="Details"
                                component={DetailsScreen}
                            />
                        </HomeStack.Navigator>
                    )}
                </Tab.Screen>
            </Tab.Navigator>
        </NavigationContainer>
    );
}

我们从HomeScreenDetailScreen,然后使用tab栏切换到SetterScreen 并导航到profileScreen ,在这一系列操作完成后,挂载了4个屏幕组件,如果你使用标签栏切换回HomeStack,你会注意到你会看到DetailsScreen - HomeStack的导航状态被保留了!

react navigation声明周期中的事件

foucs 监听对焦事件;和htmlinput标签的focus事件干的事一样

blur 监听超出焦点事件;和htmlinput标签的blur事件干的事一样

function Profile({ navigation }) {
    React.useEffect(() => {
        const unsubscribe = navigation.addListener('focus', () => {
          // Screen was focused
          // Do something
        });

      	return unsubscribe;
    }, [navigation]);

    return <ProfileContent />;
}

在函数组件中可以使用useFoucsEffect这个钩子函数来监听执行事件

import { useFocusEffect } from '@react-navigation/native';

function Profile() {
    useFocusEffect(
        React.useCallback(() => {
            // Do something when the screen is focused

            return () => {
                // Do something when the screen is unfocused
                // Useful for cleanup functions
            };
        }, [])
    );

    return <ProfileContent />;
}