
在react单页应用中,当用户执行页面刷新操作时,整个应用会重新加载。这意味着所有组件都会重新挂载,并且由usestate或react context api管理的瞬时状态都会被初始化为其默认值。对于认证状态(如用户id auth.id、认证token、用户角色等),如果这些数据仅存储在组件的内部状态或context中,那么在页面刷新后,它们将丢失,导致用户需要重新登录或应用无法获取到必要的认证信息。
在提供的代码示例中,useAuth自定义Hook通过AuthContext管理认证状态。AuthContext的AuthProvider组件内部使用useState({})来初始化auth对象。虽然在useEffect中尝试从localStorage加载数据,但如果localStorage中没有数据,或者在某些情况下useEffect的执行时机与组件渲染的依赖关系处理不当,就可能导致auth对象在初始渲染时为空,或者在刷新后未能及时从localStorage恢复。
为了解决页面刷新导致认证状态丢失的问题,最常见的做法是将认证相关的数据存储在客户端的持久化存储中,例如localStorage。localStorage允许浏览器存储键值对数据,并且这些数据在浏览器关闭后仍然存在,直到被显式清除。
实现认证状态持久化的核心思想是:
AuthContext的AuthProvider是管理全局认证状态的关键。我们需要确保它在组件挂载时尝试从localStorage加载数据。
// AuthContext.js
import { createContext, useState, useEffect } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
// auth 初始值设为 null 或一个明确的空对象,等待从 localStorage 加载
// loading 状态用于指示认证数据是否已加载完成
const [auth, setAuth] = useState(null);
const [loading, setLoading] = useState(true); // 初始为true,表示正在加载认证数据
useEffect(() => {
const loadAuthData = () => {
try {
const storedToken = localStorage.getItem("accessToken");
const storedRoles = localStorage.getItem("roles");
const storedId = localStorage.getItem("userId");
if (storedToken && storedRoles && storedId) {
// 注意:localStorage 存储的是字符串,需要 JSON.parse 解析回原始类型
const token = JSON.parse(storedToken);
const roles = JSON.parse(storedRoles);
const id = JSON.parse(storedId);
setAuth({ token, roles, id });
} else {
// 如果 localStorage 中没有完整数据,则将 auth 设置为空对象
setAuth({});
}
} catch (error) {
console.error("Failed to load auth data from localStorage:", error);
// 发生错误时也设置为空对象,并确保 loading 结束
setAuth({});
} finally {
setLoading(false); // 数据加载完毕,无论成功与否
}
};
loadAuthData();
}, []); // 空依赖数组确保只在组件挂载时执行一次
// 提供一个 logout 函数,用于清除认证状态和 localStorage
const logout = () => {
setAuth({}); // 清空 auth 状态
localStorage.removeItem("accessToken");
localStorage.removeItem("roles");
localStorage.removeItem("userId");
};
// 假设在登录成功时,你会调用 setAuth 并将数据存入 localStorage
const updateAuthAndPersist = (newAuthData) => {
setAuth(newAuthData);
localStorage.setItem("accessToken", JSON.stringify(newAuthData.token));
localStorage.setItem("roles", JSON.stringify(newAuthData.roles));
localStorage.setItem("userId", JSON.stringify(newAuthData.id));
};
return (
<AuthContext.Provider value={{ auth, setAuth: updateAuthAndPersist, loading, logout }}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;关键改进点:
在依赖认证状态的组件中(例如Exercises组件),应该利用loading状态来确保在auth数据可用后再进行操作。
// Exercises.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
function Exercises() {
const { setAuth, auth, loading } = useAuth(); // 获取 auth, setAuth, 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(() => {
// 只有当 auth 数据加载完成且 auth.id 存在时才进行 API 调用
if (!loading && auth && auth.id) {
setExerciseData((prevData) => ({
...prevData,
exerciseId: id,
date: new Date(),
}));
api.getUserExercises(id).then((response) => {
setRequests(response.data);
}).catch(error => {
console.error("Failed to fetch user exercises:", error);
setErr("Failed to load exercises.");
});
} else if (!loading && (!auth || !auth.id)) {
// 如果加载完成但 auth.id 为空,可能需要重定向到登录页
// navigate('/login');
console.log("Auth ID is null or not loaded, cannot fetch exercises.");
setRequests([]); // 清空请求,避免显示旧数据
}
}, [id, auth, loading]); // 依赖 auth 和 loading 状态
const onInputChange = (e) => {
setExerciseData({ ...exerciseData, [e.target.name]: e.target.value });
};
const onSubmit = (e) => {
e.preventDefault();
// 在提交前再次检查 auth.id 是否存在
if (!auth || !auth.id) {
setErr("User not authenticated. Please log in.");
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) {
// 这里应该调用 getUserExercises(auth.id) 而不是 (id)
// 因为 getUserExercises 可能是获取特定用户的所有练习
return api.getUserExercises(auth.id);
} else {
throw new Error("An error occurred while creating the request.");
}
})
.then((response) => {
setRequests(response.data);
setExerciseData({ ...updatedExerciseData, weight: "", reps: "" });
setErr(""); // 清除错误信息
popup(); // 显示成功提示
})
.catch((error) => {
console.error(error);
setErr("An error occurred while creating the request.");
popup(); // 显示错误提示
});
};
const popup = () => {
showPopup("exercise-popup");
setTimeout(() => showPopup("hide"), 3000);
};
// 在 auth 数据加载完成前显示加载状态
if (loading) {
return <div className={styles.wrapper}>Loading authentication data...</div>;
}
// 如果 auth.id 为空,可以显示未认证提示或重定向
if (!auth || !auth.id) {
return <div className={styles.wrapper}>Please log in to view and set exercises.</div>;
}
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>No exercises assigned yet.</p>
)}
</div>
<form onSubmit={(e) => onSubmit(e)} className={styles.exerciseForm}>
<h1 className={styles.h1Text}>
Set <br /> Exercise
</h1>
<div className={styles.inputContainer}>
<label htmlFor="weight" className={styles.inputLabel}>
Enter weight
</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}>
Enter reps
</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;关键改进点:
通过在AuthContext的AuthProvider中利用useEffect和localStorage,我们能够有效地在React应用中实现认证状态的持久化,从而解决页面刷新后auth.id等数据丢失的问题。这种方法确保了用户在刷新页面后无需重新登录,极大地提升了用户体验。同时,合理管理加载状态和遵循安全最佳实践是构建健壮可靠认证系统的关键。
以上就是React应用中认证状态持久化:避免页面刷新后Auth数据丢失的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号