React Navigation 6鉴权流程:如何正确获取服务端webToken?
解决方案
核心问题明确
你碰到的错误完全是违反了React Hook的核心规则:所有Hook必须直接在函数组件的顶层作用域调用,不能嵌套在useMemo、循环、条件判断这类子逻辑里。useMutation作为Apollo提供的Hook,自然也得遵守这个规则。
正确的代码结构方案
登录请求、token存储这类逻辑,应该放在登录页面组件的顶层,然后通过用户交互(比如点击登录按钮)来触发执行,具体步骤如下:
在登录组件顶层声明Hook
把useMutation(Apollo的登录接口)、useNavigation(React Navigation的导航)、SecureStore的操作都放在组件最外层,不要嵌套任何Hook。封装登录处理函数
写一个普通的异步函数,用来调用mutation接口、处理返回的token、存储到SecureStore,最后触发页面跳转。绑定到用户交互事件
把这个处理函数绑定到登录按钮的onPress事件上,由用户操作触发执行。
示例代码
import { View, TextInput, Button, Text } from 'react-native'; import { useMutation, gql } from '@apollo/client'; import { useNavigation } from '@react-navigation/native'; import { useState } from 'react'; import * as SecureStore from 'expo-secure-store'; // 定义登录的GraphQL mutation const LOGIN_MUTATION = gql` mutation Login($username: String!, $password: String!) { login(username: $username, password: $password) { webToken } } `; export default function LoginScreen() { // 顶层声明所有Hook,符合规则 const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [login, { loading, error }] = useMutation(LOGIN_MUTATION); const navigation = useNavigation(); // 封装登录逻辑的普通函数 const handleLogin = async () => { try { const { data } = await login({ variables: { username, password } }); // 存储token到SecureStore await SecureStore.setItemAsync('userToken', data.login.webToken); // 跳转到主页(replace避免返回登录页) navigation.replace('Home'); } catch (err) { console.error('登录失败:', err); // 这里可以添加错误提示,比如Toast组件 } }; return ( <View style={{ padding: 20, gap: 15 }}> <TextInput value={username} onChangeText={setUsername} placeholder="用户名" autoCapitalize="none" /> <TextInput value={password} onChangeText={setPassword} placeholder="密码" secureTextEntry autoCapitalize="none" /> <Button title={loading ? '登录中...' : '登录'} onPress={handleLogin} disabled={loading} /> {error && <Text style={{ color: 'red' }}>登录失败:{error.message}</Text>} </View> ); }
关于导航鉴权的补充
如果是在App启动时需要检查是否已有token,来决定展示登录页还是主页,这个逻辑应该放在根组件的useEffect里,而不是useMemo:
import { NavigationContainer } from '@react-navigation/native'; import { useEffect, useState } from 'react'; import { View, Text } from 'react-native'; import * as SecureStore from 'expo-secure-store'; // 假设你已经定义了两个导航栈 import HomeStack from './HomeStack'; import AuthStack from './AuthStack'; export default function App() { const [isLoading, setIsLoading] = useState(true); const [userToken, setUserToken] = useState(null); useEffect(() => { // 启动时检查本地存储的token const checkToken = async () => { const token = await SecureStore.getItemAsync('userToken'); setUserToken(token); setIsLoading(false); }; checkToken(); }, []); if (isLoading) { return <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>加载中...</Text> </View>; } return ( <NavigationContainer> {userToken ? <HomeStack /> : <AuthStack />} </NavigationContainer> ); }
内容的提问来源于stack exchange,提问作者JeremyQin
相关产品推荐
相关产品推荐

