Appearance
导航守卫与全局导航配置
第五部分:路由导航(页面跳转)
导航守卫是 React Navigation 中的重要概念,用于控制页面的访问权限和导航行为。本文将详细介绍如何实现导航守卫和全局导航配置。
1. 导航守卫
基本概念
导航守卫是一种机制,用于在导航发生前、导航发生时或导航发生后执行特定的逻辑。常见的导航守卫包括:
- 前置守卫:在导航开始前执行
- 后置守卫:在导航完成后执行
- 离开守卫:在离开当前页面时执行
实现导航守卫
可以使用 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 中的重要功能,用于控制页面的访问权限和导航行为。通过本文的学习,你应该掌握了以下内容:
- 导航守卫的基本概念和实现方法
- 全局导航服务的创建和使用
- 身份验证守卫的实现
- 全局导航配置(主题、导航选项)
- 导航状态持久化
- 常见问题的解决方案
- 最佳实践
在实际开发中,合理使用这些功能,可以创建出更加安全、可靠、用户友好的导航体验,提升应用的整体质量。
