GraphQL+Redux+JWT认证开发:注册正常但loadUser函数失效
问题根因与修复方案
已定位的代码硬伤
- 冗余type字段覆盖逻辑:
loadUser的成功回调里的dispatch动作同时写了type: USER_LOADED和type: LOGIN_SUCCESS,JS对象后面的键会覆盖前面的键,实际只会触发LOGIN_SUCCESS分支,USER_LOADED的逻辑永远不会执行。 - 数据取值路径错误:直接拿
res.data.data当payload,对照之前可正常运行的旧版代码,接口实际返回结构是res.data.data.userLogin才是包含user信息和token的实际数据对象,直接取顶层res.data.data会导致action.payload.token为undefined,无法写入localStorage。 - GraphQL查询字段不匹配:现在的
userLogin查询只平铺取email、password、token三个字段,和旧版能跑通的接口返回结构(userLogin下嵌套user对象+独立token字段)完全不匹配,拉取回来的字段和reducer的预期结构错位。 - 工具函数拼写错误:catch分支里的
retrunErrors是拼写错误,正确是returnErrors,接口异常时执行该方法会直接报错中断流程。 - 函数职责混淆:当前
loadUser被设计成接收email、password参数调用登录接口,和常规认证逻辑里“带本地存储token拉取当前登录用户信息”的职责完全不符,把登录和用户信息初始化两个动作混在了一起。 - 安全隐患:注册接口的查询字段里包含password字段,后端不应该返回用户明文密码,存在数据泄露风险。
对应修复代码
authAction.js 重构后的正确实现
import axios from 'axios'; // 引入所有已定义的类型常量和工具方法 import { USER_LOADING, USER_LOADED, AUTH_ERROR, LOGIN_SUCCESS, LOGIN_FAIL, REGISTER_SUCCESS, REGISTER_FAIL, returnErrors } from './types'; import { getTokenConfig } from './authUtils'; const API_URL = 'https://my-server.com/api'; // 注册动作:修正查询字段和payload取值路径 export const register = ({ name, email, password }) => dispatch => { axios.post( API_URL, { query: ` mutation { userSignup(name: "${name}", email: "${email}", password: "${password}"){ user { name email role } token } } ` } ) .then(res => { // 优先处理GraphQL层面返回的业务错误 if (res.data.errors) { dispatch(returnErrors(res.data.errors[0].message, 400)); dispatch({ type: REGISTER_FAIL }); return; } dispatch({ type: REGISTER_SUCCESS, payload: res.data.data.userSignup }) }) .catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); dispatch({ type: REGISTER_FAIL }); }) } // 拆分独立的登录动作:接收邮箱密码,完成登录拿token export const login = (email, password) => dispatch => { dispatch({ type: USER_LOADING }) axios.post( API_URL, { query: ` query { userLogin(email: "${email}", password: "${password}"){ user { name email role } token } } ` } ) .then(res => { if (res.data.errors) { dispatch(returnErrors(res.data.errors[0].message, 400)); dispatch({ type: LOGIN_FAIL }); return; } dispatch({ type: LOGIN_SUCCESS, payload: res.data.data.userLogin }) }) .catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); dispatch({ type: LOGIN_FAIL }); }) } // 真正的loadUser逻辑:应用初始化时带本地存储的token拉取用户信息 export const loadUser = () => (dispatch, getState) => { dispatch({ type: USER_LOADING }) // 从state/localStorage读取token,拼接到请求头 axios.get(API_URL, getTokenConfig(getState)) .then(res => { dispatch({ type: USER_LOADED, payload: res.data.data.currentUser }) }) .catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); dispatch({ type: AUTH_ERROR }) }) }
authReducer.js 对应分支调整
// 原有reducer逻辑补充修正 case LOGIN_SUCCESS: case REGISTER_SUCCESS: // 从payload里取token存入localStorage localStorage.setItem('token', action.payload.token) // 全局设置axios的Authorization请求头,对齐旧版逻辑 axios.defaults.headers.common['Authorization'] = `Bearer ${action.payload.token}` return { ...state, user: action.payload.user, token: action.payload.token, isAuthenticated: true, isLoading: false } case USER_LOADED: return { ...state, user: action.payload, isAuthenticated: true, isLoading: false } case AUTH_ERROR: case LOGIN_FAIL: case REGISTER_FAIL: case LOGOUT_SUCCESS: // 清理本地存储和全局请求头 localStorage.removeItem('token') delete axios.defaults.headers.common['Authorization'] return { ...state, user: null, token: null, isAuthenticated: false, isLoading: false }
落地注意事项
- 统一localStorage的存储key名:旧版代码用
userToken、user作为key,现在Redux实现用token作为key,需要在应用初始化环节做老用户的key迁移,避免登录状态失效。 - 后续把字符串拼接的GraphQL查询改成variables传参模式,避免特殊字符(比如带双引号的用户名/邮箱)触发查询语法错误。
- 在Redux store的初始化环节挂载一次
loadUser动作,实现页面刷新后自动从本地存储的token恢复登录状态的逻辑,对齐旧版的初始化行为。
内容的提问来源于stack exchange,提问作者Garrett
相关产品推荐
相关产品推荐

