Skip to content

导航守卫与全局导航配置

第五部分:路由导航(页面跳转)

导航守卫是 React Navigation 中的重要概念,用于控制页面的访问权限和导航行为。本文将详细介绍如何实现导航守卫和全局导航配置。

1. 导航守卫

基本概念

导航守卫是一种机制,用于在导航发生前、导航发生时或导航发生后执行特定的逻辑。常见的导航守卫包括:

  1. 前置守卫:在导航开始前执行
  2. 后置守卫:在导航完成后执行
  3. 离开守卫:在离开当前页面时执行

实现导航守卫

可以使用 navigation.addListener 方法来实现导航守卫:

jsx
import React, { useEffect } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

export default function ProtectedScreen({ navigation, route }) {
  useEffect(() => {
    // 前置守卫
    const unsubscribeBefore = navigation.addListener('beforeRemove', (e) => {
      // 阻止默认行为
      e.preventDefault();
      
      // 显示确认对话框
      Alert.alert(
        '确认离开',
        '你确定要离开这个页面吗?',
        [
          {
            text: '取消',
            style: 'cancel',
          },
          {
            text: '确定',
            onPress: () => navigation.dispatch(e.data.action),
          },
        ]
      );
    });

    // 后置守卫
    const unsubscribeAfter = navigation.addListener('state', (e) => {
      console.log('导航完成:', e.data.state);
    });

    return () => {
      unsubscribeBefore();
      unsubscribeAfter();
    };
  }, [navigation]);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>受保护的页面</Text>
      <Text style={styles.info}>离开此页面需要确认</Text>
      <Button
        title="返回首页"
        onPress={() => navigation.goBack()}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 20,
  },
  title: {
    fontSize: 24,
    marginBottom: 20,
  },
  info: {
    fontSize: 16,
  },
});

2. 全局导航守卫

创建导航服务

可以创建一个导航服务来管理全局导航逻辑:

jsx
// navigation/NavigationService.js
import { createRef } from 'react';

export const navigationRef = createRef();

export function navigate(name, params) {
  if (navigationRef.current) {
    navigationRef.current.navigate(name, params);
  }
}

export function goBack() {
  if (navigationRef.current) {
    navigationRef.current.goBack();
  }
}

export function reset(state) {
  if (navigationRef.current) {
    navigationRef.current.reset(state);
  }
}

配置导航容器

jsx
// App.js
import 'react-native-gesture-handler';
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { navigationRef } from './navigation/NavigationService';
import AppStackNavigator from './navigation/StackNavigator';

export default function App() {
  return (
    <NavigationContainer ref={navigationRef}>
      <AppStackNavigator />
    </NavigationContainer>
  );
}

实现全局导航守卫

jsx
// navigation/AppNavigator.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/HomeScreen';
import LoginScreen from '../screens/LoginScreen';
import ProtectedScreen from '../screens/ProtectedScreen';
import { isAuthenticated } from '../utils/auth';

const Stack = createStackNavigator();

export default function AppNavigator() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        {isAuthenticated() ? (
          // 已登录状态
          <>
            <Stack.Screen name="Home" component={HomeScreen} options={{ title: '首页' }} />
            <Stack.Screen name="Protected" component={ProtectedScreen} options={{ title: '受保护页面' }} />
          </>
        ) : (
          // 未登录状态
          <Stack.Screen name="Login" component={LoginScreen} options={{ title: '登录' }} />
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

3. 身份验证守卫

实现登录状态管理

jsx
// utils/auth.js
import AsyncStorage from '@react-native-async-storage/async-storage';

export const isAuthenticated = async () => {
  try {
    const token = await AsyncStorage.getItem('token');
    return !!token;
  } catch (error) {
    console.error('Error checking authentication:', error);
    return false;
  }
};

export const login = async (username, password) => {
  // 模拟登录
  if (username === 'admin' && password === '123456') {
    await AsyncStorage.setItem('token', 'mock-token');
    return true;
  }
  return false;
};

export const logout = async () => {
  await AsyncStorage.removeItem('token');
};

实现身份验证导航守卫

jsx
// navigation/AuthNavigator.js
import React, { useState, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/HomeScreen';
import LoginScreen from '../screens/LoginScreen';
import ProtectedScreen from '../screens/ProtectedScreen';
import { isAuthenticated } from '../utils/auth';

const Stack = createStackNavigator();

export default function AuthNavigator() {
  const [isLoading, setIsLoading] = useState(true);
  const [userToken, setUserToken] = useState(null);

  useEffect(() => {
    const checkAuth = async () => {
      const token = await isAuthenticated();
      setUserToken(token);
      setIsLoading(false);
    };

    checkAuth();
  }, []);

  if (isLoading) {
    return <LoadingScreen />;
  }

  return (
    <NavigationContainer>
      <Stack.Navigator>
        {userToken ? (
          // 已登录状态
          <>
            <Stack.Screen name="Home" component={HomeScreen} options={{ title: '首页' }} />
            <Stack.Screen name="Protected" component={ProtectedScreen} options={{ title: '受保护页面' }} />
          </>
        ) : (
          // 未登录状态
          <Stack.Screen name="Login" component={LoginScreen} options={{ title: '登录' }} />
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

function LoadingScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>加载中...</Text>
    </View>
  );
}

4. 全局导航配置

自定义主题

jsx
// navigation/theme.js
export const lightTheme = {
  dark: false,
  colors: {
    primary: '#4CAF50',
    background: '#f5f5f5',
    card: '#ffffff',
    text: '#333333',
    border: '#e0e0e0',
    notification: '#ff3b30',
  },
};

export const darkTheme = {
  dark: true,
  colors: {
    primary: '#4CAF50',
    background: '#121212',
    card: '#1e1e1e',
    text: '#ffffff',
    border: '#333333',
    notification: '#ff453a',
  },
};

配置全局主题

jsx
// App.js
import 'react-native-gesture-handler';
import React, { useState } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { lightTheme, darkTheme } from './navigation/theme';
import AppStackNavigator from './navigation/StackNavigator';

export default function App() {
  const [isDarkMode, setIsDarkMode] = useState(false);

  return (
    <NavigationContainer theme={isDarkMode ? darkTheme : lightTheme}>
      <AppStackNavigator onToggleTheme={() => setIsDarkMode(!isDarkMode)} />
    </NavigationContainer>
  );
}

全局导航选项

jsx
// navigation/StackNavigator.js
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/HomeScreen';
import DetailsScreen from '../screens/DetailsScreen';
import SettingsScreen from '../screens/SettingsScreen';

const Stack = createStackNavigator();

export default function AppStackNavigator({ onToggleTheme }) {
  return (
    <Stack.Navigator
      screenOptions={{
        headerStyle: {
          backgroundColor: '#4CAF50',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
          fontWeight: 'bold',
        },
        headerBackTitle: '返回',
      }}
    >
      <Stack.Screen 
        name="Home" 
        component={HomeScreen} 
        options={{ title: '首页' }} 
      />
      <Stack.Screen 
        name="Details" 
        component={DetailsScreen} 
        options={{ title: '详情页' }} 
      />
      <Stack.Screen 
        name="Settings" 
        component={SettingsScreen} 
        options={{
          title: '设置',
          headerRight: () => (
            <TouchableOpacity onPress={onToggleTheme} style={{ marginRight: 16 }}>
              <Text style={{ color: '#fff' }}>切换主题</Text>
            </TouchableOpacity>
          ),
        }} 
      />
    </Stack.Navigator>
  );
}

5. 导航状态持久化

安装依赖

bash
# 使用 npm
npm install @react-native-async-storage/async-storage

# 使用 yarn
yarn add @react-native-async-storage/async-storage

实现导航状态持久化

jsx
// navigation/PersistentNavigator.js
import React, { useState, useEffect } from 'react';
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import AsyncStorage from '@react-native-async-storage/async-storage';
import HomeScreen from '../screens/HomeScreen';
import DetailsScreen from '../screens/DetailsScreen';

const Stack = createStackNavigator();

const PERSISTENCE_KEY = 'NAVIGATION_STATE';

export default function PersistentNavigator() {
  const [isReady, setIsReady] = useState(false);
  const [initialState, setInitialState] = useState(null);
  const navigationRef = useNavigationContainerRef();

  useEffect(() => {
    const loadNavigationState = async () => {
      try {
        const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
        if (savedStateString) {
          setInitialState(JSON.parse(savedStateString));
        }
      } catch (e) {
        console.error('Failed to load navigation state', e);
      } finally {
        setIsReady(true);
      }
    };

    loadNavigationState();
  }, []);

  const onStateChange = async (state) => {
    try {
      await AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state));
    } catch (e) {
      console.error('Failed to save navigation state', e);
    }
  };

  if (!isReady) {
    return null;
  }

  return (
    <NavigationContainer
      ref={navigationRef}
      initialState={initialState}
      onStateChange={onStateChange}
    >
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} options={{ title: '首页' }} />
        <Stack.Screen name="Details" component={DetailsScreen} options={{ title: '详情页' }} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

6. 常见问题与解决方案

问题 1:导航守卫不生效

问题:导航守卫没有按预期执行。

解决方案

  • 确保正确添加了导航监听器
  • 确保在组件卸载时清理监听器
  • 检查导航事件名称是否正确

问题 2:身份验证状态管理

问题:身份验证状态无法正确管理。

解决方案

  • 使用 AsyncStorage 或其他持久化存储
  • 实现全局状态管理
  • 确保在应用启动时检查登录状态

问题 3:导航状态持久化失败

问题:导航状态无法正确持久化。

解决方案

  • 确保安装了 AsyncStorage
  • 检查存储权限
  • 确保导航状态是可序列化的

问题 4:全局主题切换

问题:全局主题切换不生效。

解决方案

  • 确保正确配置了主题
  • 确保主题对象符合 React Navigation 的要求
  • 检查主题切换逻辑是否正确

7. 最佳实践

1. 组织导航代码

将导航相关代码组织到单独的目录中:

navigation/
  ├── NavigationService.js    # 导航服务
  ├── StackNavigator.js      # 栈导航器
  ├── TabNavigator.js        # 标签导航器
  ├── AuthNavigator.js       # 身份验证导航器
  ├── theme.js               # 主题配置
  └── types.ts               # 类型定义

2. 使用导航服务

使用导航服务进行全局导航:

jsx
// 在任何地方使用导航服务
import { navigate } from './navigation/NavigationService';

// 导航到其他页面
navigate('Details', { id: 1, title: '产品详情' });

3. 实现完整的身份验证流程

jsx
// screens/LoginScreen.js
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, Alert } from 'react-native';
import { login } from '../utils/auth';
import { navigate } from '../navigation/NavigationService';

export default function LoginScreen() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);

  const handleLogin = async () => {
    if (!username || !password) {
      Alert.alert('错误', '请输入用户名和密码');
      return;
    }

    setLoading(true);
    try {
      const success = await login(username, password);
      if (success) {
        navigate('Home');
      } else {
        Alert.alert('错误', '用户名或密码错误');
      }
    } catch (error) {
      Alert.alert('错误', '登录失败,请重试');
      console.error('Login error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>登录</Text>
      <TextInput
        style={styles.input}
        value={username}
        onChangeText={setUsername}
        placeholder="用户名"
        marginBottom={10}
      />
      <TextInput
        style={styles.input}
        value={password}
        onChangeText={setPassword}
        placeholder="密码"
        secureTextEntry
        marginBottom={20}
      />
      <Button
        title={loading ? '登录中...' : '登录'}
        onPress={handleLogin}
        disabled={loading}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    marginBottom: 20,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
    width: '100%',
  },
});

4. 优化导航性能

  • 使用 React.memo 优化导航组件
  • 合理使用 lazy 加载
  • 避免在导航事件中执行繁重的操作
  • 定期清理导航状态

8. 总结

导航守卫和全局导航配置是 React Navigation 中的重要功能,用于控制页面的访问权限和导航行为。通过本文的学习,你应该掌握了以下内容:

  1. 导航守卫的基本概念和实现方法
  2. 全局导航服务的创建和使用
  3. 身份验证守卫的实现
  4. 全局导航配置(主题、导航选项)
  5. 导航状态持久化
  6. 常见问题的解决方案
  7. 最佳实践

在实际开发中,合理使用这些功能,可以创建出更加安全、可靠、用户友好的导航体验,提升应用的整体质量。

© 2026 编程马·菜鸟教程 版权所有