
在 react 应用中,当用户刷新页面时,整个应用会重新加载。这意味着所有组件的状态(通过 usestate 或 usereducer 管理)都会被重置为其初始值。对于认证信息(如用户id、令牌、角色),如果它们仅存储在组件或上下文的内存状态中,刷新后就会丢失,导致用户需要重新登录或应用无法识别其身份。
即使在自定义 Hook (如 useAuth) 中使用了 useEffect 来从 localStorage 读取数据,也存在一个时间差:
在提供的代码示例中,Exercises 组件在 useEffect 和 onSubmit 中都直接使用了 auth.id。当页面刷新时,useAuth 钩子内部的 auth 状态会先被初始化为 {},尽管 AuthProvider 会尝试从 localStorage 加载数据,但 Exercises 组件可能在 auth.id 尚未被正确赋值之前就尝试访问它,导致 auth.id 为 null 或 undefined。
为了解决这个问题,我们需要确保在认证数据从 localStorage 加载完成之前,依赖这些数据的组件不会进行操作。这可以通过在认证上下文中引入一个 loading 状态来管理。
AuthProvider 的职责是在应用启动时尝试从 localStorage 加载持久化的认证信息,并提供一个 loading 状态来指示数据是否已加载完成。
// AuthContext.js
import { createContext, useState, useEffect } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
const [loading, setLoading] = useState(true); // 初始为 true,表示正在加载
useEffect(() => {
// 尝试从 localStorage 读取认证信息
const storedToken = localStorage.getItem("accessToken");
const storedRoles = localStorage.getItem("roles");
const storedId = localStorage.getItem("userId");
if (storedToken && storedRoles && storedId) { // 确保所有关键信息都存在
try {
const token = JSON.parse(storedToken);
const roles = JSON.parse(storedRoles);
const id = JSON.parse(storedId);
// 如果成功解析,则更新 auth 状态
setAuth({ token, roles, id });
} catch (e) {
// 解析失败,可能是数据损坏,清空 localStorage
console.error("Failed to parse stored auth data:", e);
localStorage.removeItem("accessToken");
localStorage.removeItem("roles");
localStorage.removeItem("userId");
}
}
setLoading(false); // 数据加载尝试完成后,设置 loading 为 false
}, []); // 仅在组件挂载时运行一次
// 假设还有一个 logout 函数,用于清除认证状态和 localStorage
const logout = () => {
setAuth({});
localStorage.removeItem("accessToken");
localStorage.removeItem("roles");
localStorage.removeItem("userId");
};
return (
<AuthContext.Provider value={{ auth, setAuth, loading, logout }}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;关键点:
在任何需要访问 auth 信息的组件中(例如 Exercises 组件),都应该在 loading 状态为 false 之前避免使用 auth 对象中的敏感数据,如 auth.id。
// ExercisePage.js
import React, { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import styles from "./ExercisePage.module.css";
import api from "../../apis/requestService";
import useAuth from "../../hooks/useAuth"; // 假设 useAuth 钩子返回 auth, setAuth, loading
function Exercises() {
const { auth, loading } = useAuth(); // 获取 auth 和 loading 状态
const { id } = useParams();
const navigate = useNavigate();
const [requests, setRequests] = useState([]);
const [exerciseData, setExerciseData] = useState({
weight: "",
reps: "",
exerciseId: id,
date: null,
});
const [err, setErr] = useState("");
const [popupStyle, showPopup] = useState("hide");
const { weight, reps } = exerciseData;
useEffect(() => {
setExerciseData((prevData) => ({
...prevData,
exerciseId: id,
date: new Date(),
}));
// 只有当 loading 为 false 且 auth.id 存在时才发起请求
if (!loading && auth.id) {
api.getUserExercises(id).then((response) => {
setRequests(response.data);
}).catch(error => {
console.error("Error fetching user exercises:", error);
setErr("无法加载训练记录。");
});
}
}, [id, auth.id, loading]); // 依赖中加入 loading
const onInputChange = (e) => {
setExerciseData({ ...exerciseData, [e.target.name]: e.target.value });
};
const onSubmit = (e) => {
e.preventDefault();
// 在提交前再次检查 auth.id 是否可用
if (loading || !auth.id) {
setErr("认证信息尚未加载或无效,请稍候再试或重新登录。");
popup();
return;
}
console.log("User id: " + auth.id);
const updatedExerciseData = {
...exerciseData,
userId: auth.id,
date: new Date(),
};
api
.createRequest(updatedExerciseData)
.then((response) => {
if (response.data.id) {
// 创建成功后,重新获取用户训练记录
return api.getUserExercises(auth.id);
} else {
throw new Error("创建请求时发生错误。");
}
})
.then((response) => {
setRequests(response.data);
setExerciseData({ ...updatedExerciseData, weight: "", reps: "" }); // 清空输入
setErr(""); // 清除错误信息
popup(); // 显示成功提示
})
.catch((error) => {
console.error(error);
setErr("创建请求时发生错误:" + (error.response?.data?.message || error.message));
popup();
});
};
const popup = () => {
showPopup("exercise-popup");
setTimeout(() => showPopup("hide"), 3000);
};
// 如果正在加载认证信息,可以显示加载状态或不渲染依赖 auth 的部分
if (loading) {
return <div className={styles.wrapper}>加载认证信息...</div>;
}
// 如果 auth.id 不存在(例如未登录),可以重定向或显示提示
if (!auth.id) {
return <div className={styles.wrapper}>请登录以查看和设置训练。</div>;
// 或者 navigate('/login');
}
return (
<div className={styles.wrapper}>
{/* ... 现有渲染逻辑 ... */}
<div className={styles.content}>
{requests.length > 0 ? (
requests.map((request, index) => (
<div key={index} className={styles.requestBox}>
<div className={styles.requestDetails}>
<h2>{request.exercise.name}</h2>
<p>{request.exercise.description}</p>
</div>
<img
src={request.exercise.imageUrl}
alt={request.exercise.name}
/>
<div className={styles.requestInfo}>
<p>Weight: {request.weight} kg</p>
<p>Reps: {request.reps}</p>
<p>Date: {new Date(request.date).toLocaleDateString()}</p>
</div>
</div>
))
) : (
<p>暂无训练记录。</p>
)}
</div>
<form onSubmit={(e) => onSubmit(e)} className={styles.exerciseForm}>
<h1 className={styles.h1Text}>
设置 <br /> 训练
</h1>
<div className={styles.inputContainer}>
<label htmlFor="weight" className={styles.inputLabel}>
输入重量
</label>
<input
id="weight"
name="weight"
type="number"
value={weight}
min="0"
onChange={onInputChange}
className={styles.inputBox}
/>
</div>
<div className={styles.inputContainer}>
<label htmlFor="reps" className={styles.inputLabel}>
输入次数
</label>
<input
id="reps"
name="reps"
type="number"
value={reps}
min="0"
onChange={onInputChange}
className={styles.inputBox}
/>
</div>
<button className={styles.exerciseBtn} type="submit">
+
</button>
<div className={popupStyle}>
<h3>{err}</h3>
</div>
</form>
</div>
);
}
export default Exercises;关键点:
数据敏感性与 localStorage 安全:
JSON.parse 和 JSON.stringify:
用户体验:
其他持久化方案:
通过以上改进,您的 React 应用将能够更稳健地管理用户认证状态,即使在页面刷新后也能保持用户登录状态,提供更流畅的用户体验。
以上就是React 应用中刷新页面后认证状态丢失的解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号