You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Navigation 6鉴权流程:如何正确获取服务端webToken?

解决方案

核心问题明确

你碰到的错误完全是违反了React Hook的核心规则:所有Hook必须直接在函数组件的顶层作用域调用,不能嵌套在useMemo、循环、条件判断这类子逻辑里。useMutation作为Apollo提供的Hook,自然也得遵守这个规则。

正确的代码结构方案

登录请求、token存储这类逻辑,应该放在登录页面组件的顶层,然后通过用户交互(比如点击登录按钮)来触发执行,具体步骤如下:

  1. 在登录组件顶层声明Hook
    把useMutation(Apollo的登录接口)、useNavigation(React Navigation的导航)、SecureStore的操作都放在组件最外层,不要嵌套任何Hook。

  2. 封装登录处理函数
    写一个普通的异步函数,用来调用mutation接口、处理返回的token、存储到SecureStore,最后触发页面跳转。

  3. 绑定到用户交互事件
    把这个处理函数绑定到登录按钮的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 18:52:35