React自定义受保护路由加载子组件时传递的userInfo props为空如何解决
问题原因
useEffect回调不支持直接传入async函数,React要求useEffect的返回值只能是清理函数,async函数返回的Promise会被当作无效的清理函数,存在运行隐患。- 核心导致子组件拿到空
userInfo的原因是状态更新顺序错误:你在收到接口200响应后,先执行了setIsLoggedIn(true),此时React会触发组件重渲染、渲染受保护的子组件,而此时你还没有执行await r.json()获取用户信息,userInfo还是初始的空对象。等后续拿到用户信息执行setUserInfo后,才会第二次触发重渲染,给子组件传入正确的userInfo,所以子组件第一次挂载拿到的是空对象。 - 缺少加载状态处理:接口请求未返回前,
isLoggedIn为初始false,如果你的路由逻辑有未登录跳转的规则,会先触发跳转再收到登录状态,逻辑异常。
修复方案
1. 基础修复(解决子组件拿到空userInfo的问题)
调整状态更新顺序,先拿到用户信息后再同时更新登录状态和用户信息,同时修正useEffect的异步写法:
const UserAuthenticatedRoute = ({component: Component, ...rest}) => { const [isLoggedIn, setIsLoggedIn] = useState(false); const [userInfo, setUserInfo] = useState({}); const [loading, setLoading] = useState(true); // 新增加载状态 useEffect(() => { // 把异步逻辑写在内部函数里,不要直接给useEffect传async const checkAuth = async () => { try { const r = await userService.isUserLoggedIn(); if (r.status === 200){ const userInfo = await r.json(); // 拿到所有数据后再同时更新状态 setUserInfo(userInfo); setIsLoggedIn(true); } else { setIsLoggedIn(false); // 未登录可以在这里跳转到登录页,比如用useNavigate跳转 } } catch (e) { setIsLoggedIn(false); // 接口报错也按未登录处理 } finally { setLoading(false); // 请求完成后关闭加载态 } } checkAuth(); }, []); // 请求中先渲染加载态,不要提前渲染路由内容 if (loading) return <div>加载中...</div> return ( <Route {...rest} render={props => ( <main> {isLoggedIn && <Component {...props} userInfo={userInfo}/>} </main> )} /> ); };
改完之后,只有等用户信息拿到之后才会把isLoggedIn设为true,此时渲染子组件拿到的就是完整的userInfo。
2. 优化方案(避免逐层传userInfo)
如果受保护路由下的多层级组件都需要用到userInfo,建议用React Context来做全局状态共享,不用每层都通过props传递:
- 先创建UserContext
import { createContext, useContext, useState, useEffect } from 'react'; const UserContext = createContext(null); // 封装Context Provider export const UserProvider = ({children}) => { const [userInfo, setUserInfo] = useState({}); const [isLoggedIn, setIsLoggedIn] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { const checkAuth = async () => { // 和上面的checkAuth逻辑一致,拿到数据后更新状态 try { const r = await userService.isUserLoggedIn(); if (r.status === 200){ const user = await r.json(); setUserInfo(user); setIsLoggedIn(true); } } catch(e) { setIsLoggedIn(false); } finally { setLoading(false); } } checkAuth(); }, []) if (loading) return <div>加载中...</div> return <UserContext.Provider value={{isLoggedIn, userInfo}}>{children}</UserContext.Provider> } // 封装自定义hook方便子组件取数据 export const useUser = () => useContext(UserContext);
- 改造受保护路由,直接用Context里的状态:
const UserAuthenticatedRoute = ({component: Component, ...rest}) => { const {isLoggedIn, userInfo} = useUser(); return ( <Route {...rest} render={props => ( <main> {isLoggedIn && <Component {...props} userInfo={userInfo}/>} </main> )} /> ); };
- 根组件包裹
UserProvider:
root.render( <UserProvider> <BrowserRouter> {/* 你的所有路由逻辑 */} </BrowserRouter> </UserProvider> )
之后所有子组件都可以直接用const {userInfo} = useUser()拿到用户信息,不用逐层传props。
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

