feat: user 依赖优化
This commit is contained in:
@@ -489,6 +489,11 @@ export default function HomeNavbar() {
|
|||||||
const brandHover = useColorModeValue('blue.600', 'blue.300');
|
const brandHover = useColorModeValue('blue.600', 'blue.300');
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
// ⚡ 提取 userId 为独立变量,避免 user 对象引用变化导致无限循环
|
||||||
|
const userId = user?.id;
|
||||||
|
const prevUserIdRef = React.useRef(userId);
|
||||||
|
const prevIsAuthenticatedRef = React.useRef(isAuthenticated);
|
||||||
|
|
||||||
// 添加调试信息
|
// 添加调试信息
|
||||||
logger.debug('HomeNavbar', '组件渲染状态', {
|
logger.debug('HomeNavbar', '组件渲染状态', {
|
||||||
hasUser: !!user,
|
hasUser: !!user,
|
||||||
@@ -727,29 +732,44 @@ export default function HomeNavbar() {
|
|||||||
error: error.message
|
error: error.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, user?.id]); // 只依赖 user.id,避免 user 对象变化导致无限循环
|
}, [isAuthenticated, userId]); // ⚡ 使用 userId 而不是 user?.id
|
||||||
|
|
||||||
// 监听用户变化,重置检查标志(用户切换或退出登录时)
|
// 监听用户变化,重置检查标志(用户切换或退出登录时)
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
const userIdChanged = prevUserIdRef.current !== userId;
|
||||||
|
const authChanged = prevIsAuthenticatedRef.current !== isAuthenticated;
|
||||||
|
|
||||||
|
if (userIdChanged || authChanged) {
|
||||||
|
prevUserIdRef.current = userId;
|
||||||
|
prevIsAuthenticatedRef.current = isAuthenticated;
|
||||||
|
|
||||||
if (!isAuthenticated || !user) {
|
if (!isAuthenticated || !user) {
|
||||||
// 用户退出登录,重置标志
|
// 用户退出登录,重置标志
|
||||||
hasCheckedCompleteness.current = false;
|
hasCheckedCompleteness.current = false;
|
||||||
setProfileCompleteness(null);
|
setProfileCompleteness(null);
|
||||||
setShowCompletenessAlert(false);
|
setShowCompletenessAlert(false);
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, user?.id]); // 监听用户 ID 变化
|
}
|
||||||
|
}, [isAuthenticated, userId, user]); // ⚡ 使用 userId
|
||||||
|
|
||||||
// 用户登录后检查资料完整性
|
// 用户登录后检查资料完整性
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isAuthenticated && user) {
|
const userIdChanged = prevUserIdRef.current !== userId;
|
||||||
|
const authChanged = prevIsAuthenticatedRef.current !== isAuthenticated;
|
||||||
|
|
||||||
|
if ((userIdChanged || authChanged) && isAuthenticated && user) {
|
||||||
// 延迟检查,避免过于频繁
|
// 延迟检查,避免过于频繁
|
||||||
const timer = setTimeout(checkProfileCompleteness, 1000);
|
const timer = setTimeout(checkProfileCompleteness, 1000);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, user?.id, checkProfileCompleteness]); // 只依赖 user.id,避免无限循环
|
}, [isAuthenticated, userId, checkProfileCompleteness, user]); // ⚡ 使用 userId
|
||||||
|
|
||||||
// 加载订阅信息
|
// 加载订阅信息
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
const userIdChanged = prevUserIdRef.current !== userId;
|
||||||
|
const authChanged = prevIsAuthenticatedRef.current !== isAuthenticated;
|
||||||
|
|
||||||
|
if (userIdChanged || authChanged) {
|
||||||
if (isAuthenticated && user) {
|
if (isAuthenticated && user) {
|
||||||
const loadSubscriptionInfo = async () => {
|
const loadSubscriptionInfo = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -785,7 +805,8 @@ export default function HomeNavbar() {
|
|||||||
is_active: true
|
is_active: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, user?.id]); // 只依赖 user.id 而不是整个 user 对象
|
}
|
||||||
|
}, [isAuthenticated, userId, user]); // ⚡ 使用 userId,防重复通过 ref 判断
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/components/ProtectedRoute.js - 弹窗拦截版本
|
// src/components/ProtectedRoute.js - 弹窗拦截版本
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { Box, VStack, Spinner, Text } from '@chakra-ui/react';
|
import { Box, VStack, Spinner, Text } from '@chakra-ui/react';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { useAuthModal } from '../contexts/AuthModalContext';
|
import { useAuthModal } from '../contexts/AuthModalContext';
|
||||||
@@ -8,15 +8,17 @@ const ProtectedRoute = ({ children }) => {
|
|||||||
const { isAuthenticated, isLoading, user } = useAuth();
|
const { isAuthenticated, isLoading, user } = useAuth();
|
||||||
const { openAuthModal, isAuthModalOpen } = useAuthModal();
|
const { openAuthModal, isAuthModalOpen } = useAuthModal();
|
||||||
|
|
||||||
// 记录当前路径,登录成功后可以跳转回来
|
// ⚡ 使用 useRef 保存当前路径,避免每次渲染创建新字符串导致 useEffect 无限循环
|
||||||
const currentPath = window.location.pathname + window.location.search;
|
const currentPathRef = useRef(window.location.pathname + window.location.search);
|
||||||
|
|
||||||
// 未登录时自动弹出认证窗口
|
// 未登录时自动弹出认证窗口
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading && !isAuthenticated && !user && !isAuthModalOpen) {
|
if (!isLoading && !isAuthenticated && !user && !isAuthModalOpen) {
|
||||||
openAuthModal(currentPath);
|
openAuthModal(currentPathRef.current);
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, user, isLoading, isAuthModalOpen, currentPath, openAuthModal]);
|
// ⚠️ 移除 user 依赖,因为 user 对象每次从 API 返回都是新引用,会导致无限循环
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isAuthenticated, isLoading, isAuthModalOpen, openAuthModal]);
|
||||||
|
|
||||||
// 显示加载状态
|
// 显示加载状态
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export const AuthProvider = ({ children }) => {
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { showWelcomeGuide } = useNotification();
|
const { showWelcomeGuide } = useNotification();
|
||||||
|
|
||||||
|
// ⚡ 使用 ref 保存最新的 isAuthenticated 值,避免事件监听器重复注册
|
||||||
|
const isAuthenticatedRef = React.useRef(isAuthenticated);
|
||||||
|
|
||||||
// 检查Session状态
|
// 检查Session状态
|
||||||
const checkSession = async () => {
|
const checkSession = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -57,19 +60,27 @@ export const AuthProvider = ({ children }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data.isAuthenticated && data.user) {
|
if (data.isAuthenticated && data.user) {
|
||||||
setUser(data.user);
|
// ⚡ 只在 user 数据真正变化时才更新状态,避免无限循环
|
||||||
setIsAuthenticated(true);
|
setUser((prevUser) => {
|
||||||
|
// 比较用户 ID,如果相同则不更新
|
||||||
|
if (prevUser && prevUser.id === data.user.id) {
|
||||||
|
return prevUser;
|
||||||
|
}
|
||||||
|
return data.user;
|
||||||
|
});
|
||||||
|
setIsAuthenticated((prev) => prev === true ? prev : true);
|
||||||
} else {
|
} else {
|
||||||
setUser(null);
|
setUser((prev) => prev === null ? prev : null);
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated((prev) => prev === false ? prev : false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('AuthContext', 'checkSession', error);
|
logger.error('AuthContext', 'checkSession', error);
|
||||||
// 网络错误或超时,设置为未登录状态
|
// 网络错误或超时,设置为未登录状态
|
||||||
setUser(null);
|
setUser((prev) => prev === null ? prev : null);
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated((prev) => prev === false ? prev : false);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
// ⚡ 只在 isLoading 为 true 时才设置为 false,避免不必要的状态更新
|
||||||
|
setIsLoading((prev) => prev === false ? prev : false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,11 +90,17 @@ export const AuthProvider = ({ children }) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// ⚡ 同步 isAuthenticated 到 ref
|
||||||
|
useEffect(() => {
|
||||||
|
isAuthenticatedRef.current = isAuthenticated;
|
||||||
|
}, [isAuthenticated]);
|
||||||
|
|
||||||
// 监听路由变化,检查session(处理微信登录回调)
|
// 监听路由变化,检查session(处理微信登录回调)
|
||||||
|
// ⚡ 移除 isAuthenticated 依赖,使用 ref 避免重复注册事件监听器
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleRouteChange = () => {
|
const handleRouteChange = () => {
|
||||||
// 如果是从微信回调返回的,重新检查session
|
// 使用 ref 获取最新的认证状态
|
||||||
if (window.location.pathname === '/home' && !isAuthenticated) {
|
if (window.location.pathname === '/home' && !isAuthenticatedRef.current) {
|
||||||
checkSession();
|
checkSession();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -91,7 +108,7 @@ export const AuthProvider = ({ children }) => {
|
|||||||
window.addEventListener('popstate', handleRouteChange);
|
window.addEventListener('popstate', handleRouteChange);
|
||||||
return () => window.removeEventListener('popstate', handleRouteChange);
|
return () => window.removeEventListener('popstate', handleRouteChange);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isAuthenticated]);
|
}, []); // ✅ 空依赖数组,只注册一次事件监听器
|
||||||
|
|
||||||
// 更新本地用户的便捷方法
|
// 更新本地用户的便捷方法
|
||||||
const updateUser = (partial) => {
|
const updateUser = (partial) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user