【发布时间】:2022-01-14 18:54:42
【问题描述】:
我正在使用 react-router-dom、typescript、react 和 Apollo graphql-generator & client。
我希望处理 4 个场景:
- 路由对登录和注销的用户开放
- 路由只对登录用户开放
- 路由只对注销的用户开放
- 路由对存储在 db 上的组策略成员的用户开放
我不想通过 props 管理状态,而是使用类似 Redux 的方法来管理状态,使用 Apollo Client 中的某些东西。
到目前为止我得到的最接近的是通过反应变量(见下面的代码)。
但是,我宁愿避免使用它们,并坚持使用 Apollo 查询。
我们在 GraphQL 中有一个返回当前登录用户的查询,但是,我似乎无法在登录时运行和更新查询,因此它可以用于检查路由。那是除非我在 App 文件中创建一个状态,并将其注入到 Login 组件中以进行更新。然后,当 Login 重定向到新的路由时,App 文件中的组件,使用刚刚更新的 userState,可以检查 userState 是否授权 Login 重定向到的路由。
不过,正如我上面所说,我想避免通过 props 传递状态。
目前的实现是基于这个:https://v5.reactrouter.com/web/example/auth-workflow
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import { HashRouter, Redirect, Route, Switch, useHistory } from 'react-router-dom'
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
makeVar,
} from '@apollo/client'
// -------------------------- client.js -------------------------------------------------
const cache = new InMemoryCache();
// set userVar initially to null, so if !null then logged in
export const userVar = makeVar(null)
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache
});
// --------------------------- routes.js ------------------------------------------------
const ROUTES = {
HOME: '/', // Only accessible by logged-in users
LOGIN: '/login', // Only accessible by users NOT logged-in
ABOUT: '/about', // Accessible by all logged-in / and not logged-in users
NOTFOUND: '/notFound',
}
const { PUBLIC, AUTH, GUEST } = {
PUBLIC: 0,
AUTH: 1,
GUEST: 2,
}
const AuthRoute = props => {
const { path, restrictTo, redirectPath, ...routeProps } = props
console.log("Inside AuthRoute")
console.table({path, restrictTo, redirectPath, ...routeProps})
const isAuthorized = to => {
const authOnly = !!(userVar() ?? false)
console.log(`authOnly = ${ authOnly }`)
console.log(`to = ${ to }`)
const allowAll = true
switch (to) {
case PUBLIC:
console.log(`PUBLIC --> isAuthorized --> allowAll = ${ allowAll }`)
return allowAll
case AUTH:
console.log(`AUTH --> isAuthorized --> authOnly = ${ authOnly }`)
return authOnly
case GUEST:
console.log(`GUEST --> isAuthorized --> !authOnly = ${ !authOnly }`)
return !authOnly
}
}
if (isAuthorized(restrictTo)) {
console.log(`Authorized -- Routing to ${ path }`)
console.log(`Authorized -- routeProps = `)
console.table({...routeProps})
return <Route {...routeProps} />
} else {
console.log(`--> NOT Authorized -- Redirecting to ${ redirectPath }`)
return <Redirect to={ redirectPath } />
}
}
// ------------------------ home.js -----------------------------------------
const Home = () => {
const history = useHistory()
const signOut = () => {
// Do auth reset here
userVar(null) //reset global state to logged-out
history.push(ROUTES.LOGIN)
}
return (
<div>
<h1>Home - Private Page</h1>
<button onClick={ signOut }>Sign Out</button>
</div>
)
}
// ------------------------ about.js -----------------------------------------
const About = () => {
return (
<div>
<h1>About - Public Page</h1>
</div>
)
}
// ------------------------ notfound.js -----------------------------------------
const NotFound = () => {
return (
<div>
<h1>404 - Public Page</h1>
</div>
)
}
// ------------------------ login.js -----------------------------------------
const Login = ({onSubmit}) => {
console.log(`--> Inside Login`)
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const history = useHistory()
const onLogin = e => {
e.preventDefault()
//Do email/password auth here
userVar(email) //Set global state to logged-in
history.push(ROUTES.HOME)
}
return (
<div>
<h1>LOGIN</h1>
<form onSubmit={ onLogin }>
<label for="uemail"><b>Email</b></label>
<input
type="text"
placeholder="Enter Email"
name="uemail"
value={ email }
onChange={ (e) => setEmail( e.target.value ) }
required
/>
<label for="upassword"><b>Password</b></label>
<input
type="password"
placeholder="Enter Password"
name="upassword"
value={ password }
onChange={ (e) => setPassword( e.target.value ) }
required
/>
<button type="submit">Login</button>
</form>
</div>
)
}
// ------------------------ index.js ---------------------------------------------
ReactDOM.render(
<React.StrictMode>
<HashRouter>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</HashRouter>
</React.StrictMode>,
document.getElementById("root"),
)
// ------------------------ App.js ---------------------------------------------
function App() {
return (
<Switch>
<AuthRoute exact
path={ROUTES.HOME}
restrictTo={AUTH}
redirectPath={ROUTES.LOGIN}
>
<Home />
</AuthRoute>
<AuthRoute
path={ROUTES.LOGIN}
restrictTo={GUEST}
redirectPath={ROUTES.HOME}
>
<Login />
</AuthRoute>
<AuthRoute
path={ROUTES.ABOUT}
restrictTo={PUBLIC}
redirectPath={ROUTES.ABOUT}
>
<About />
</AuthRoute>
<AuthRoute
path={ROUTES.NOTFOUND}
restrictTo={PUBLIC}
redirectPath={ROUTES.NOTFOUND}
>
<NotFound />
</AuthRoute>
// Catch-all Route -- could send to 404 if you want
<Route>
<Redirect to={ROUTES.NOTFOUND} />
</Route>
</Switch>
)
}
<script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@apollo/client@3.3.2/apollo-client.cjs.min.js"></script>
<script src="https://unpkg.com/react-router-dom@5.2.0/umd/react-router-dom.min.js"></script>
<script src="https://unpkg.com/react-router@5.2.0/umd/react-router.min.js"></script>
<div id="root"></div>
【问题讨论】:
-
我知道这是一个自我回答,但您的问题过于宽泛,缺少minimal, complete, and reproducible code example,并且缺少正式声明和任何具体问题的详细信息。它更像是一个代码编写服务请求,这是 Stackoverflow 所没有的,而且非常离题。
-
这是一个中肯的评论。如果这不是一个自我回答,我会重写。但是,由于我的答案中的代码提供了我想要实现的全部细节,我觉得它可以保持原样。我什至发布的唯一原因是看看是否有人可以改进我的解决方案。
-
如果你正在编写代码并且你正在寻找更多的代码审查,那么我建议codereview.stackexchange.com。
-
谢谢。我不知道 codereview.stack... 存在。
-
我已经修正了问题和答案,以更好地匹配所提供的指南。感谢您的建设性反馈。
标签: reactjs typescript react-router apollo apollo-client