【发布时间】:2022-01-19 18:07:58
【问题描述】:
我正在处理路由身份验证,并将身份验证状态存储在上下文中,以便其他 React 组件可以检查用户是否已登录。代码的相关部分是:
const [loggedInUser, setLoggedInUser] = useState(null)
const authed = () => !!loggedInUser
useEffect(() => {
async function fetchData() {
const response = await fetch('/loggedInUser')
.then(res => res.json())
setLoggedInUser(response)
}
fetchData()
}, [])
此代码的快速解释是,我需要部分代码中的用户数据(例如 id),因此我需要存储 loggedInUser 对象。但是,对于更简单的任务,例如检查用户是否已登录,我使用函数 authed 来检查 loggedInUser 变量是否包含对象(用户已登录)或为空。
为了检查用户是否已登录,我使用的是 passport.js,我的 '/loggedInUser' 路由如下所示:
app.get('/loggedInUser', async (req, res) => {
if (!req.isAuthenticated())
return null
const { id, name } = await req.user
.then(res => res.dataValues)
return res.json({ id, name })
})
问题是,在useEffect() 和fetch() 能够访问GET 路由并使用对setLoggedInUser(response) 的API 响应之前,使用此上下文并检查authed() 的代码正在运行。所以使用authed() 总是返回false,即使API 响应稍后将loggedInUser 设置为现在authed() 为true 的某个对象值。显然这是一个竞争条件,但我不知道如何解决它。
是否有一个优雅的解决方案,我可以“锁定”authed() 从返回值直到 useEffect() 完全“设置”loggedInUser 的状态?
我设想的一个(糟糕的)解决方案可能是这样的:
const [loggedInUser, setLoggedInUser] = useState(null)
const isFinishedLoading = false // <--
function authed() {
while (!isFinishedLoading) {} // a crude lock
return !!loggedInUser
}
useEffect(() => {
async function fetchData() {
const response = await fetch('/loggedInUser')
.then(res => res.json())
setLoggedInUser(response)
isFinishedLoading = true // <--
}
fetchData()
}, [])
有没有更好的方法来“锁定”函数authed()直到加载完成?
编辑:
为了澄清我对 cmets 的 authed() 用法,这里是我的 App.js 的精简版
export default function App() {
return (
<>
<AuthProvider>
<Router className="Router">
<ProtectedRoute path="/" component={SubmittalTable} />
<Login path="/login" />
</Router>
</AuthProvider>
</>
)
}
function ProtectedRoute({ component: Component, ...rest }){
const { authed } = useContext(AuthContext)
if (!authed()) // due to race condition, authed() is always false
return (<Redirect from="" to="login" noThrow />)
return (<Component {...rest} />)
}
【问题讨论】:
-
不要混用
await和.then。他们正在尝试做类似的事情。 -
“上下文”是指 React.Context 吗?你传递给提供者什么?您是否尝试将
isLoggedIn提供给 Provider? -
这相当广泛,每次页面刷新时您都会发送一个 http 请求以检查用户是否仍然登录。我建议查看一些有关会话管理的文章。
-
您将难以实现“锁定”。一旦验证了身份验证并加载了数据,您是否只能通过条件语句呈现数据?或者,如果用户未通过身份验证,则将用户重定向到登录页面,并在确认身份验证后重定向回来?您可以将身份验证存储在集中状态,然后在请求从 api 返回 401 时重定向用户。无论哪种方式,您都不能阻止组件的渲染。
-
@AxisStarstreamer 不,我将其用作 AuthContext,类似于reactjs.org/docs/context.html#when-to-use-context。我将 value={{isLoggedIn, authed}} 传递给提供者,但这并不能解决我的竞争条件问题。
isLoggedIn将共享相同的竞争条件问题。
标签: javascript node.js reactjs asynchronous race-condition