React AWS认证重定向流程中useEffect执行两次的处理方案咨询
问题
我正在开发一个使用AWS Authentication(支持社交及非社交登录)的小型应用。在AWS的重定向登录流程中,我在useEffect内订阅登录事件,并通过POST请求向服务器创建用户。
但useEffect执行了两次(已通过日志确认),查Stack Overflow得知这是<React.StrictMode>导致的,且useEffect应保持纯函数特性。请问在此场景下,推荐的账户创建流程是什么?
现有代码
export default function LoginRedirect() { const _userService = new UserService(); const navigator = useNavigate(); useEffect(() => { console.log("activating useEffect in LoginRedirect"); const getUserAndProcess = async (eventCancelToken: () => void) => { try { console.log("processing user..."); const currentUser = await Auth.currentAuthenticatedUser() as CognitoUser if(currentUser != null) eventCancelToken() //stop listening to any more auth events else return; const userSession = currentUser.getSignInUserSession(); const idToken = userSession?.getIdToken().getJwtToken(); const accessToken = userSession?.getAccessToken().getJwtToken(); const refreshToken = userSession?.getRefreshToken().getToken(); if(idToken === undefined || accessToken === undefined || refreshToken === undefined) { throw new Error("idToken, accessToken, or refreshToken is undefined"); } const processUserResult = await _userService.processUser( { IdToken: idToken } as ProcessUser ); console.log(currentUser); console.log(processUserResult); navigator("/"); } catch(error) { console.warn(error); } }; const authListenerCancelToken = Hub.listen("auth", ({ payload: { event, data }}) => { switch (event) { case 'signIn': console.log('Sign in happened!', data); getUserAndProcess(authListenerCancelToken) .catch(console.error); break; case 'cognitoHostedUI': console.log('Sign in happened via cognito hosted UI!', data); break; case "signOut": break; case 'signIn_failure': case 'cognitoHostedUI_failure': console.log('Sign in failure', data); break; } }); getUserAndProcess(authListenerCancelToken).catch(console.error); }); return ( <div className="h-screen w-full grid place-items-center"> <div className="grid place-items-center"> <h1 className="text-lg my-3">Logging you in...</h1> <div className="loading loading-ring loading-lg justify-self-center"></div> </div> </div> ); }
推荐的账户创建流程及问题修复
1. 处理StrictMode导致的重复执行
StrictMode在开发环境下会刻意执行两次useEffect(含清理函数),用于检测潜在bug。要避免重复触发账户创建逻辑,需做以下调整:
- 添加执行标记:用变量记录是否已处理过用户,防止重复调用
processUser。 - 完善清理逻辑:在
useEffect的清理阶段调用Hub的取消监听函数,避免内存泄漏和重复监听。
修改后的useEffect示例:
useEffect(() => { let isUserProcessed = false; // 标记是否已完成用户处理 console.log("activating useEffect in LoginRedirect"); const getUserAndProcess = async (eventCancelToken: () => void) => { if (isUserProcessed) return; // 已处理过则直接返回 try { console.log("processing user..."); const currentUser = await Auth.currentAuthenticatedUser() as CognitoUser if(!currentUser) return; isUserProcessed = true; // 标记为已处理 eventCancelToken(); // 停止监听后续auth事件 const userSession = currentUser.getSignInUserSession(); const idToken = userSession?.getIdToken().getJwtToken(); const accessToken = userSession?.getAccessToken().getJwtToken(); const refreshToken = userSession?.getRefreshToken().getToken(); if(!idToken || !accessToken || !refreshToken) { throw new Error("idToken, accessToken, or refreshToken is undefined"); } const processUserResult = await _userService.processUser( { IdToken: idToken } as ProcessUser ); console.log(currentUser); console.log(processUserResult); navigator("/"); } catch(error) { console.warn(error); } }; const authListenerCancelToken = Hub.listen("auth", ({ payload: { event, data }}) => { switch (event) { case 'signIn': case 'cognitoHostedUI': console.log('Sign in happened!', data); getUserAndProcess(authListenerCancelToken).catch(console.error); break; case "signOut": break; case 'signIn_failure': case 'cognitoHostedUI_failure': console.log('Sign in failure', data); break; } }); getUserAndProcess(authListenerCancelToken).catch(console.error); // 清理函数:取消auth事件监听 return () => { authListenerCancelToken(); }; }, []); // 空依赖数组确保useEffect仅在组件挂载时执行(开发环境下StrictMode会执行两次,但清理逻辑会同步触发)
2. 优化账户创建核心流程
- 服务器端幂等性保障:即使前端因意外重复调用
processUser,服务器需通过Cognito ID Token中的唯一标识(如sub字段)判断用户是否已存在,存在则返回现有信息,不存在再创建账户。 - 分离逻辑职责:将用户处理逻辑抽离为独立工具函数或自定义Hook,保持组件简洁,同时便于复用和测试。
- 全局状态管理:将用户信息存入Context或状态管理库(如Redux),避免在多个组件中重复获取用户数据。
3. 其他优化点
- 缓存服务实例:避免在组件内部每次渲染都创建
UserService实例,可将实例提升到组件外部,或用useMemo缓存:const _userService = useMemo(() => new UserService(), []); - 增强错误处理:针对不同类型的错误(如网络异常、Token无效)添加针对性提示,提升用户体验。
内容的提问来源于stack exchange,提问作者FailedUnitTest
相关产品推荐
相关产品推荐

