导航返回TeacherAttend组件时考勤数据重复获取的原因排查
核心原因分析
1. AttendenceProvider随路由反复卸载/挂载
你的路由配置中,AttendenceProvider仅包裹了Admin路由的index子路由(path: ""):
{ path: "/admin", element: <PrivateRoute roles={["Admin"]}><Admin /></PrivateRoute>, children: [ { path: "", element: ( <AttendenceProvider type="Teacher"> <Teachersattend /> </AttendenceProvider> ), }, // 其他Admin子路由... ] }
当你导航到其他Admin子路由(比如/admin/teachers)时,Teachersattend组件和它外层的AttendenceProvider会被完全卸载;当你返回/admin时,Provider会重新挂载,触发内部的useEffect再次执行fetchData,导致重复请求。
2. 开发环境下StrictMode的双重渲染
React的StrictMode会在开发环境中强制组件渲染两次(包括useEffect的执行),这会让你看到两次请求,但这个问题仅存在于开发环境,生产环境不会出现。不过这不是导航返回重复请求的根本原因。
3. 组件本地状态与Context的冗余同步
Teacherattend组件中用本地useState初始化了teacherList和AttendenceData,但没有监听Context数据的更新,这不仅会导致本地状态与Context不同步,还会增加不必要的渲染开销,但不是重复请求的直接原因。
解决方案
1. 提升AttendenceProvider到更高层级
将AttendenceProvider移动到Admin路由的根元素中,让它在整个Admin路由生命周期内保持挂载状态:
// 修改Admin路由配置 { path: "/admin", element: ( <PrivateRoute roles={["Admin"]}> <AttendenceProvider type="Teacher"> <Admin /> </AttendenceProvider> </PrivateRoute> ), children: [ { path: "", element: <Teachersattend /> }, { path: "teachers", element:<AdminTeachers />}, // 其他子路由保持不变... ], }
这样切换Admin子路由时,AttendenceProvider不会被卸载,内部的状态和数据会被保留,返回时不会重新请求。
2. 添加数据缓存逻辑(可选)
如果需要确保即使Provider重新挂载(比如页面刷新)也不会重复请求,可以在AttendenceProvider中添加缓存判断:
// 在AttendenceProvider中添加ref跟踪请求状态 const hasFetched = useRef(false); const fetchData = useCallback(async () => { // 已请求过则直接返回 if (hasFetched.current) return; hasFetched.current = true; if (type === "Teacher") { let teacherData = await getRequest("http://localhost:5000/api/getTeacher"); teacherData = Object.values(teacherData.data).flat(); let attendData = await sendJSONRequest( "http://localhost:5000/api/getAttendence", { type: type } ); attendData = Object.values(attendData.data).flat(); processData(teacherData, attendData); } else if (type === "Student") {} }, [type]);
3. 移除组件内的冗余本地状态
将Teacherattend中的本地状态逻辑迁移到AttendenceContext中,直接使用Context数据:
// 修改Teacherattend组件 const Teacherattend = React.memo(() => { const { list, attendence, updateAttendenceStatus } = useContext(AttendenceContext); // 移除本地state,直接使用Context数据 const handleStatus = (attendeId) => { // 调用Context中的方法更新状态 updateAttendenceStatus(attendeId); }; // ...其他逻辑保持不变 return ( <div className="py-6 px-2 lg:px-4 bg-white shadow-md rounded-lg"> {list.length !== 0 ? <Attendance name="Teacher Attendance" data={list} onStatusChange={handleStatus} /> : "Loading.."} {/* ...其他元素 */} </div> ); });
同时在AttendenceProvider中添加updateAttendenceStatus方法:
const AttendenceProvider = ({ children, type }) => { // ...原有状态 const updateAttendenceStatus = useCallback((attendeId) => { setList(prevList => prevList.map(data => data.attendeId === attendeId ? { ...data, status: data.status === "Present" ? "Absent" : "Present" } : data ) ); }, []); const Data = useMemo(() => ({ list, attendence, updateGrade, updateAttendenceStatus }), [list, attendence, updateGrade, updateAttendenceStatus]); // ...原有返回逻辑 };
内容的提问来源于stack exchange,提问作者BI IYI

