React中SetInterval的应用及无刷新登录注册注销认证方案
React无刷新认证实现方案
无刷新认证的核心是前端维护全局认证状态,通过异步API完成身份交互,配合路由控制页面访问权限,全程无需整页刷新。以下是具体实现方案:
一、全局认证状态管理
首先要在前端统一管理用户的认证状态(是否登录、用户信息、令牌等),常用两种方案:
1. React Context + useReducer(中小型项目首选)
通过Context全局共享认证状态,useReducer处理状态变更逻辑:
// AuthContext.js import { createContext, useContext, useReducer, useEffect } from 'react'; const AuthContext = createContext(); const authReducer = (state, action) => { switch (action.type) { case 'LOGIN_SUCCESS': localStorage.setItem('token', action.payload.token); localStorage.setItem('refreshToken', action.payload.refreshToken); return { ...state, user: action.payload.user, isAuthenticated: true }; case 'LOGOUT': localStorage.removeItem('token'); localStorage.removeItem('refreshToken'); return { ...state, user: null, isAuthenticated: false }; case 'REGISTER_SUCCESS': return { ...state, message: action.payload.message }; default: return state; } }; export const AuthProvider = ({ children }) => { const [state, dispatch] = useReducer(authReducer, { user: null, isAuthenticated: false, message: '' }); // 初始化时从本地存储恢复登录状态 useEffect(() => { const token = localStorage.getItem('token'); if (token) { // 调用API验证token有效性,有效则同步用户信息 fetch('/api/user/profile', { headers: { Authorization: `Bearer ${token}` } }) .then(res => res.json()) .then(user => dispatch({ type: 'LOGIN_SUCCESS', payload: { user, token } })) .catch(() => localStorage.removeItem('token')); } }, []); // 监听多标签页状态同步 useEffect(() => { const handleStorageChange = (e) => { if (e.key === 'token' && !e.newValue) { dispatch({ type: 'LOGOUT' }); } }; window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); }, [dispatch]); return ( <AuthContext.Provider value={{ state, dispatch }}> {children} </AuthContext.Provider> ); }; export const useAuth = () => useContext(AuthContext);
2. Redux Toolkit(大型项目)
对于复杂项目,用Redux Toolkit管理状态更规范,通过createAsyncThunk处理异步登录/注册请求,createSlice维护状态逻辑,核心思路和Context方案一致,只是状态管理的载体不同。
二、登录/注册/注销的异步交互
所有身份操作通过API异步完成,请求成功后更新前端状态:
1. 登录操作
// Login.jsx import { useState } from 'react'; import { useAuth } from './AuthContext'; import { useNavigate } from 'react-router-dom'; const Login = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const { dispatch } = useAuth(); const navigate = useNavigate(); const handleLogin = async (e) => { e.preventDefault(); try { const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await res.json(); if (res.ok) { dispatch({ type: 'LOGIN_SUCCESS', payload: data }); navigate('/dashboard'); // 登录成功跳转至权限页 } else { alert(data.message || '登录失败'); } } catch (err) { alert('网络异常,请稍后重试'); } }; return ( <form onSubmit={handleLogin}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" required /> <button type="submit">登录</button> </form> ); };
2. 注册操作
注册成功后可选择自动登录或引导至登录页:
// Register.jsx import { useState } from 'react'; import { useAuth } from './AuthContext'; import { useNavigate } from 'react-router-dom'; const Register = () => { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const { dispatch } = useAuth(); const navigate = useNavigate(); const handleRegister = async (e) => { e.preventDefault(); try { const res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, password }) }); const data = await res.json(); if (res.ok) { // 注册成功自动登录 dispatch({ type: 'LOGIN_SUCCESS', payload: data }); navigate('/dashboard'); // 若需手动登录则跳转至登录页:navigate('/login') } else { alert(data.message || '注册失败'); } } catch (err) { alert('网络异常,请稍后重试'); } }; return ( <form onSubmit={handleRegister}> <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="用户名" required /> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" required /> <button type="submit">注册</button> </form> ); };
3. 注销操作
清除本地令牌,更新认证状态后跳转至登录页:
// Navbar.jsx import { useAuth } from './AuthContext'; import { useNavigate } from 'react-router-dom'; const Navbar = () => { const { state, dispatch } = useAuth(); const navigate = useNavigate(); const handleLogout = () => { dispatch({ type: 'LOGOUT' }); navigate('/login'); }; return ( <nav> {state.isAuthenticated ? ( <> <span>欢迎,{state.user.name}</span> <button onClick={handleLogout}>注销</button> </> ) : ( <> <a href="/login">登录</a> <a href="/register">注册</a> </> )} </nav> ); };
三、路由守卫(保护权限页面)
通过自定义组件控制未认证用户无法访问需要权限的页面:
// ProtectedRoute.jsx import { Navigate } from 'react-router-dom'; import { useAuth } from './AuthContext'; const ProtectedRoute = ({ children }) => { const { state } = useAuth(); if (!state.isAuthenticated) { // 未登录跳转至登录页 return <Navigate to="/login" replace />; } return children; }; // 路由配置中使用 <Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
四、令牌自动刷新(可选)
若使用JWT令牌,为避免token过期后用户重新登录,可封装带自动刷新逻辑的请求工具:
// api.js const fetchWithAuth = async (url, options = {}) => { const token = localStorage.getItem('token'); const headers = { 'Content-Type': 'application/json', ...options.headers, ...(token && { Authorization: `Bearer ${token}` }) }; let res = await fetch(url, { ...options, headers }); // 401状态码表示token过期,尝试刷新 if (res.status === 401) { const refreshToken = localStorage.getItem('refreshToken'); if (refreshToken) { const refreshRes = await fetch('/api/auth/refresh-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }) }); const refreshData = await refreshRes.json(); if (refreshRes.ok) { localStorage.setItem('token', refreshData.token); // 重新发起原请求 res = await fetch(url, { ...options, headers: { ...headers, Authorization: `Bearer ${refreshData.token}` } }); } else { // 刷新失败,强制注销 localStorage.removeItem('token'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; } } else { window.location.href = '/login'; } } return res; }; // 使用示例 const getUserProfile = async () => { const res = await fetchWithAuth('/api/user/profile'); return res.json(); };
五、安全注意事项
- 令牌存储:优先使用
httpOnlycookie存储令牌,避免XSS攻击;若必须存在前端,需防范XSS风险 - 通信安全:所有API请求需开启HTTPS,防止令牌被窃听
- 权限校验:前端路由守卫仅做辅助,后端必须对每个权限接口做令牌有效性校验
内容的提问来源于stack exchange,提问作者Vasim Shaikh
相关产品推荐
相关产品推荐

