根据每条路线(受保护的路线)与Apollo进行授权,以进行当地州管理



我使用的是react路由器dom、typescript、react和Apollo graphql生成器&客户

我希望处理4种情况:

  1. 向登录和注销用户开放的路由
  2. 仅对登录用户开放路由
  3. 路由仅对已注销的用户开放
  4. 路由打开给属于存储在数据库中的组策略成员的用户

我不想通过道具管理状态,而是使用类似Redux的方法进行状态管理,在Apollo Client中使用一些东西

到目前为止,我得到的最接近的是通过反应变量(见下面的代码(。

然而,我宁愿避免使用它们,并坚持使用Apollo查询。

我们在GraphQL中有一个查询,它返回当前登录的用户,然而,我似乎无法在登录时运行和更新查询,因此它可以用于检查路由。除非我在应用程序文件中创建一个状态,并将其注入Login组件以供其更新。然后,当Login重定向到新路由时,应用程序文件的组件(具有刚刚更新的userState(可以检查userState以授权Login重定向的路由。

不过,正如我在上面所说,我想避免通过道具来传递状态。

目前的实施是基于这一点: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>

这个问题的答案是使用Apollo的LazyQuery,这是一个实际上可以在回调中使用的查询(包括钩子等(。

我们使用Apollo代码生成GraphQL,在深入研究生成的代码后,我确实注意到它生成了getCurrentUserQuery和getCurrentUserLazyQuery。

此外,虽然这个解决方案确实传递了一个道具,但它是一种不可变的使用,只深入一层,不会再传递,所以我觉得这是一个不错的折衷方案,因为我可以在其他地方调用Apollo Query来查找当前用户,当我需要的时候。

因此,我去掉了整个AuthRoute/ProtectedRoute组件,只做了以下操作(仅相关代码(:

// In App.tsx
// other code is around the code contained here... this is just a snippet
const [getUser, { data }] = useGetCurrentUserLazyQuery({})
let location = useLocation()
useAsyncEffect(     // Also works with useEffect, but Async cuts down on calls
async isMounted => {
await getUser()
if (!isMounted()) return
},
[location]
)

return (
<div className="App">
<AppRoutes email={data?.getCurrentUser?.email} />
</div>
)



// In AppRoutes.tsx
// other code is around the code contained here... this is just a snippet
// email prop type declaration is ... email: string | undefined
const { email } = props
const isLoggedIn = !!(email ?? false)

return (
<Switch>
<Route path={ROUTES.LOGIN}>
{isLoggedIn ? <Redirect to={ROUTES.HOME} /> : <Login />}
</Route>
<Route path={ROUTES.HOME}>
{!isLoggedIn ? (
<Redirect to={ROUTES.LOGIN} />
) : (
<Redirect to={DEFAULT_PATH} />
)}
</Route>
)

最新更新