我使用 React Router 4 进行路由,使用 Apollo Client 进行数据获取和缓存。我需要根据以下标准实现 PrivateRoute 和重定向解决方案:
允许用户查看的页面基于其用户状态,可以从服务器获取,也可以从缓存中读取。用户状态本质上是一组标志,我们用来了解用户在 channel 中的位置。示例标志:
isLoggedIn
、isOnboarded
、isWaitlisted
等如果用户的状态不允许他们进入该页面,则该页面甚至不应开始呈现。例如,如果您不是
isWaitlisted
,则您不应看到等候名单页面。当用户意外发现自己位于这些页面时,应该将他们重定向到适合其状态的页面。重定向也应该是动态的。例如,假设您在
isLoggedIn
之前尝试查看您的用户个人资料。然后我们需要将您重定向到登录页面。但是,如果您是isLoggedIn
但不是isOnboarded
,我们仍然不希望您看到自己的个人资料。因此,我们希望将您重定向到入门页面。所有这些都需要在路线级别上进行。页面本身应该不知道这些权限和重定向。
总之,我们需要一个提供用户状态数据的库,可以
- 计算用户是否可以访问某个页面
- 计算需要动态重定向到的位置
- 在渲染任何页面之前执行这些操作
- 在路线级别执行这些操作
我已经在开发一个通用库,但它现在有其缺点。我正在寻求关于如何解决这个问题的意见,以及是否有既定的模式来实现这一目标。
这是我目前的方法。这不起作用,因为 getRedirectPath
需要的数据位于 OnboardingPage 组件
中。
此外,我无法使用可以注入(inject)计算重定向路径所需的 props 的 HOC 来包装 PrivateRoute,因为这不会让我将它用作 Switch React Router 组件的子组件,因为它不再是 Route。
<PrivateRoute
exact
path="/onboarding"
isRender={(props) => {
return props.userStatus.isLoggedIn && props.userStatus.isWaitlistApproved;
}}
getRedirectPath={(props) => {
if (!props.userStatus.isLoggedIn) return '/login';
if (!props.userStatus.isWaitlistApproved) return '/waitlist';
}}
component={OnboardingPage}
/>
最佳答案
一般方法
我将创建一个 HOC 来处理所有页面的此逻辑。
// privateRoute is a function...
const privateRoute = ({
// ...that takes optional boolean parameters...
requireLoggedIn = false,
requireOnboarded = false,
requireWaitlisted = false
// ...and returns a function that takes a component...
} = {}) => WrappedComponent => {
class Private extends Component {
componentDidMount() {
// redirect logic
}
render() {
if (
(requireLoggedIn && /* user isn't logged in */) ||
(requireOnboarded && /* user isn't onboarded */) ||
(requireWaitlisted && /* user isn't waitlisted */)
) {
return null
}
return (
<WrappedComponent {...this.props} />
)
}
}
Private.displayName = `Private(${
WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'
})`
hoistNonReactStatics(Private, WrappedComponent)
// ...and returns a new component wrapping the parameter component
return Private
}
export default privateRoute
那么你只需要更改导出路由的方式:
export default privateRoute({ requireLoggedIn: true })(MyRoute);
您可以像今天在react-router中一样使用该路由:
<Route path="/" component={MyPrivateRoute} />
重定向逻辑
如何设置此部分取决于几个因素:
- 如何确定用户是否已登录、已加入、已列入候补名单等。
- 您希望负责重定向到哪个组件。
处理用户状态
由于您使用 Apollo,您可能只想使用 graphql
来获取 HOC 中的数据:
return graphql(gql`
query ...
`)(Private)
然后您可以修改 Private
组件来获取这些 Prop :
class Private extends Component {
componentDidMount() {
const {
userStatus: {
isLoggedIn,
isOnboarded,
isWaitlisted
}
} = this.props
if (requireLoggedIn && !isLoggedIn) {
// redirect somewhere
} else if (requireOnboarded && !isOnboarded) {
// redirect somewhere else
} else if (requireWaitlisted && !isWaitlisted) {
// redirect to yet another location
}
}
render() {
const {
userStatus: {
isLoggedIn,
isOnboarded,
isWaitlisted
},
...passThroughProps
} = this.props
if (
(requireLoggedIn && !isLoggedIn) ||
(requireOnboarded && !isOnboarded) ||
(requireWaitlisted && !isWaitlisted)
) {
return null
}
return (
<WrappedComponent {...passThroughProps} />
)
}
}
重定向到哪里
您可以在几个不同的地方处理这个问题。
简单方法:路由是静态的
如果用户未登录,您始终希望路由到 /login?return=${currentRoute}
。
在这种情况下,您可以在 componentDidMount
中对这些路由进行硬编码。完成。
组件负责
如果您希望 MyRoute
组件确定路径,您只需向 privateRoute
函数添加一些额外的参数,然后在导出 时将它们传入即可我的路线
。
const privateRoute = ({
requireLoggedIn = false,
pathIfNotLoggedIn = '/a/sensible/default',
// ...
}) // ...
然后,如果您想覆盖默认路径,请将导出更改为:
export default privateRoute({
requireLoggedIn: true,
pathIfNotLoggedIn: '/a/specific/page'
})(MyRoute)
路线负责
如果您希望能够从路由传递路径,则需要在 Private
中接收这些属性
class Private extends Component {
componentDidMount() {
const {
userStatus: {
isLoggedIn,
isOnboarded,
isWaitlisted
},
pathIfNotLoggedIn,
pathIfNotOnboarded,
pathIfNotWaitlisted
} = this.props
if (requireLoggedIn && !isLoggedIn) {
// redirect to `pathIfNotLoggedIn`
} else if (requireOnboarded && !isOnboarded) {
// redirect to `pathIfNotOnboarded`
} else if (requireWaitlisted && !isWaitlisted) {
// redirect to `pathIfNotWaitlisted`
}
}
render() {
const {
userStatus: {
isLoggedIn,
isOnboarded,
isWaitlisted
},
// we don't care about these for rendering, but we don't want to pass them to WrappedComponent
pathIfNotLoggedIn,
pathIfNotOnboarded,
pathIfNotWaitlisted,
...passThroughProps
} = this.props
if (
(requireLoggedIn && !isLoggedIn) ||
(requireOnboarded && !isOnboarded) ||
(requireWaitlisted && !isWaitlisted)
) {
return null
}
return (
<WrappedComponent {...passThroughProps} />
)
}
}
Private.propTypes = {
pathIfNotLoggedIn: PropTypes.string
}
Private.defaultProps = {
pathIfNotLoggedIn: '/a/sensible/default'
}
然后你的路线可以重写为:
<Route path="/" render={props => <MyPrivateComponent {...props} pathIfNotLoggedIn="/a/specific/path" />} />
组合选项 2 和 3
(这是我喜欢使用的方法)
您还可以让组件和路由选择负责人。您只需为路径添加 privateRoute
参数,就像我们让组件决定一样。然后使用这些值作为您的 defaultProps
,就像我们在路由负责时所做的那样。
这使您可以灵活地做出决定。请注意,将路由作为 props 传递将优先于从组件传递到 HOC。
现在大家在一起
下面是一个结合了上面所有概念的片段,用于最终呈现 HOC:
const privateRoute = ({
requireLoggedIn = false,
requireOnboarded = false,
requireWaitlisted = false,
pathIfNotLoggedIn = '/login',
pathIfNotOnboarded = '/onboarding',
pathIfNotWaitlisted = '/waitlist'
} = {}) => WrappedComponent => {
class Private extends Component {
componentDidMount() {
const {
userStatus: {
isLoggedIn,
isOnboarded,
isWaitlisted
},
pathIfNotLoggedIn,
pathIfNotOnboarded,
pathIfNotWaitlisted
} = this.props
if (requireLoggedIn && !isLoggedIn) {
// redirect to `pathIfNotLoggedIn`
} else if (requireOnboarded && !isOnboarded) {
// redirect to `pathIfNotOnboarded`
} else if (requireWaitlisted && !isWaitlisted) {
// redirect to `pathIfNotWaitlisted`
}
}
render() {
const {
userStatus: {
isLoggedIn,
isOnboarded,
isWaitlisted
},
pathIfNotLoggedIn,
pathIfNotOnboarded,
pathIfNotWaitlisted,
...passThroughProps
} = this.props
if (
(requireLoggedIn && !isLoggedIn) ||
(requireOnboarded && !isOnboarded) ||
(requireWaitlisted && !isWaitlisted)
) {
return null
}
return (
<WrappedComponent {...passThroughProps} />
)
}
}
Private.propTypes = {
pathIfNotLoggedIn: PropTypes.string,
pathIfNotOnboarded: PropTypes.string,
pathIfNotWaitlisted: PropTypes.string
}
Private.defaultProps = {
pathIfNotLoggedIn,
pathIfNotOnboarded,
pathIfNotWaitlisted
}
Private.displayName = `Private(${
WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'
})`
hoistNonReactStatics(Private, WrappedComponent)
return graphql(gql`
query ...
`)(Private)
}
export default privateRoute
我正在使用hoist-non-react-statics正如 the official documentation 中所建议的.
关于reactjs - 如何使用Apollo Client + React Router实现私有(private)路由和根据用户状态重定向?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48692649/