
本教程旨在解决 react–redux 应用中未登录用户不必要地请求用户数据和敏感 api key 导致 401 错误的问题。我们将通过在 redux thunk 中引入认证状态检查机制,并结合组件层面的状态判断,实现用户数据的按需加载,从而优化应用性能并提升用户体验。
在构建基于 react 和 Redux 的认证应用时,一个常见的挑战是如何确保只有在用户登录后才尝试加载其专属数据或需要认证的敏感资源。如果应用在初始化时无条件地发起这些请求,未登录用户将会收到大量的 401 Unauthorized 错误,这不仅会污染开发者工具的控制台,还会造成不必要的网络请求,影响应用性能。
问题根源分析
根据提供的代码示例,问题主要来源于 app.js 中的 useEffect 钩子:
- store.dispatch(loadUser()):在应用启动时无条件地分发 loadUser action。
- axios.get(‘/api/stripeapikey’):同样在应用启动时无条件地请求 Stripe API Key。
loadUser action 内部通过 axios.get(‘/api/profile’) 尝试获取用户资料。如果用户当前没有有效的认证会话(例如,尚未登录或认证 Token 已过期),后端 API 会拒绝这些请求并返回 401 状态码,从而导致前端出现错误。
解决方案:基于认证状态的条件加载
为了解决这个问题,核心思想是在发起这些需要认证的请求之前,先检查用户的认证状态。我们可以通过以下两种主要方式实现:
- 在 Redux Thunk 内部检查: 在 loadUser action creator 中,利用 Redux Thunk 提供的 getState 函数访问当前 Redux 状态,判断用户是否已认证。这种方法将认证检查逻辑封装在 action 内部,使得 loadUser 无论从何处调用都能保持一致的按需加载行为。
- 在组件中检查: 在 App.js 的 useEffect 中,使用 useSelector 获取认证状态,然后有条件地 dispatch loadUser 或请求 Stripe API Key。
本教程将结合这两种方法:loadUser 动作在 Thunk 内部进行认证检查,而 Stripe API Key 的请求则在 App.js 中基于 Redux 状态进行判断。
实现用户数据的条件加载
首先,我们需要修改 loadUser Redux Thunk,引入 getState 参数来访问 Redux store 的当前状态。在发起 API 请求之前,检查 getState().auth.isAuthenticated(假设 auth reducer 中有一个 isAuthenticated 字段表示用户是否登录)。
// actions/user.js import axios from 'axios'; export const loadUser = () => async (dispatch, getState) => { // 在尝试加载用户之前,检查用户是否已认证 // 假设 Redux 状态中有一个 auth.isAuthenticated 字段 if (!getState().auth.isAuthenticated) { // 如果用户未认证,则直接返回,不执行后续的 API 请求 // 这样可以避免在未登录状态下发起不必要的 /api/profile 请求 return; } try { dispatch({ type: 'LOAD_USER_REQ' }); // 注意:根据您的代理配置,API 路径可能是 '/api/profile' const { data } = await axios.get('/api/profile'); dispatch({ type: 'LOAD_USER_SUCCESS', payload: data.user }); } catch (error) { // 即使进行了预检查,API 请求仍可能因其他原因失败(如 token 过期), // 此时应处理错误,并将认证状态设为 false。 dispatch({ type: 'LOAD_USER_FAIL', payload: error.response && error.response.data.message ? error.response.data.message : error.message }); } }; export const clearErrors = () => async dispatch => { dispatch({ type: 'CLEAR_ERRORS' }); };
对应的 Reducer 状态管理:
为了使上述检查生效,authReducer 必须正确地管理 isAuthenticated 状态。isAuthenticated 应该在用户成功登录/注册后设为 true,在登出或加载用户失败时设为 false。
// reducers/user.js const initialState = { user: null, // 初始用户数据设为 null isAuthenticated: false, // 初始认证状态为 false loading: false, error: null }; export const authReducer = (state = initialState, action) => { switch (action.type) { case 'LOAD_USER_REQ': return { ...state, loading: true, error: null }; case 'LOAD_USER_SUCCESS': return { ...state, loading: false, isAuthenticated: true, user: action.payload, error: null }; case 'LOAD_USER_FAIL': return { ...state, loading: false, isAuthenticated: false, // 加载失败,视为未认证 user: null, // 清空用户数据 error: action.payload }; // 假设您有登录/注册/登出相关的 action // case 'LOGIN_SUCCESS': // case 'REGISTER_SUCCESS': // return { ...state, loading: false, isAuthenticated: true, user: action.payload, error: null }; // case 'LOgoUT_SUCCESS': // return { ...state, loading: false, isAuthenticated: false, user: null, error: null }; case 'CLEAR_ERRORS': return { ...state, error: null }; default: return state; } };
实现 Stripe API Key 的条件加载
对于 Stripe API Key,由于它在 App.js 的 useEffect 中直接通过 axios 请求,我们可以利用 useSelector 从 Redux store 中获取认证状态,然后有条件地执行请求。
// App.js import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { useEffect, useState } from 'react'; import { Elements } from '@stripe/react-stripe-js'; import { loadStripe } from '@stripe/stripe-js'; import axios from 'axios'; import { useSelector } from 'react-redux'; // 导入 useSelector import Header from './components/Header'; import Home from './components/Home'; import Payment from './components/Payment'; import Profile from './components/Profile'; import { loadUser } from './actions/user'; import store from './store'; export default function App() { const [stripeApiKey, setStripeApiKey] = useState(''); // 从 Redux store 中获取认证状态 const { isAuthenticated } = useSelector(state => state.auth); useEffect(() => { // 无论用户是否认证,都尝试 dispatch loadUser。 // loadUser thunk 内部会根据 isAuthenticated 状态决定是否发起 API 请求。 store.dispatch(loadUser()); // 只有当用户已认证时才请求 Stripe API Key if (isAuthenticated) { (async () => { try { const { data } = await axios.get('/api/stripeapikey'); setStripeApiKey(data.stripeApiKey); } catch (error) { console.error('Failed to load Stripe API Key:', error); // 可以在此添加错误处理,例如 dispatch 一个 action 来更新 Redux 状态 } })(); } else { // 如果用户未认证,确保清空或重置 stripeApiKey setStripeApiKey(''); } }, [isAuthenticated]); // 依赖 isAuthenticated,当认证状态改变时重新运行此 effect return ( <Router> <div className="App"> <Header /> <Routes> <Route path="/" element={<Home />} /> {/* 这些路由可能需要额外的路由保护


