>允许用户查看的页面基于用户状态,可以从服务器获取,也可以从缓存中读取.用户状态本质上是一组标志,用于了解用户在我们的渠道中的位置.示例标志:isLoggedIn,isOnboarded,isWaitlisted等.
>如果用户的状态不允许他们在该页面上,则不应该开始呈现页面.例如,如果您不是isWaitlisted,则不应该看到等待列表页面.当用户意外地发现自己在这些页面上时,应将其重定向到适合其状态的页面.
>重定向也应该是动态的.例如,假设您在isLoggedIn之前尝试查看用户配置文件.然后我们需要将您重定向到登录页面.但是,如果您是isLoggedIn但不是isOnboarded,我们仍然不希望您看到您的个人资料.因此,我们希望将您重定向到新手入门页面.
>所有这些都需要在路线层面上进行.页面本身应该不知道这些权限&重定向.
总之,我们需要一个给用户状态数据的库,可以
>计算用户是否可以在某个页面上
>计算需要动态重定向的位置
>在渲染任何页面之前执行这些操作
>在路线级别执行这些操作
我已经在开发一个通用库,但它现在有它的缺点.我正在寻求关于如何解决这个问题的意见,以及是否有既定的模式来实现这一目标.
这是我目前的做法.这不起作用,因为getRedirectPath需要的数据位于OnboardingPage组件中.
此外,我无法将PrivateRoute包装为可以注入计算重定向路径所需的道具的HOC,因为这不会让我将其用作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' })` // ...and returns a new component wrapping the parameter component return hoistNonReactStatics(Private,WrappedComponent) } export default privateRoute
然后,您只需要更改导出路线的方式:
export default privateRoute({ requireLoggedIn: true })(MyRoute);
并且您可以像在今天的react-router中那样使用该路由:
<Route path="/" component={MyPrivateRoute} />
重定向逻辑
如何设置此部分取决于几个因素:
>如何确定用户是否已登录,登机,等候等.
>您希望在哪个组件中负责重定向到哪里.
处理用户状态
既然你正在使用Apollo,你可能只想使用graphql来获取你的HOC中的数据:
return hoistNonReactStatics( graphql(gql` query ... `)(Private),WrappedComponent )
class Private extends Component { componentDidMount() { const { userStatus: { isLoggedIn,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,isWaitlisted },...passThroughProps } = this.props if ( (requireLoggedIn && !isLoggedIn) || (requireOnboarded && !isOnboarded) || (requireWaitlisted && !isWaitlisted) ) { return null } return ( <WrappedComponent {...passThroughProps} /> ) } }
在哪里重定向
您可以使用几个不同的地方来处理这个问题.
简单方法:路线是静态的
如果用户未登录,您总是希望路由到/ login?return = ${currentRoute}.
在这种情况下,您可以在componentDidMount中对这些路由进行硬编码.完成.
该组件负责
如果您希望MyRoute组件确定路径,您可以在privateRoute函数中添加一些额外的参数,然后在导出MyRoute时将其传入.
const privateRoute = ({ requireLogedIn = false,pathIfNotLoggedIn = '/a/sensible/default',// ... }) // ...
然后,如果要覆盖默认路径,请将导出更改为:
export default privateRoute({ requireLoggedIn: true,pathIfNotLoggedIn: '/a/specific/page' })(MyRoute)
路线是负责任的
如果您希望能够从路由传递路径,您将希望在私有中接收这些路径
class Private extends Component { componentDidMount() { const { userStatus: { isLoggedIn,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,// we don't care about these for rendering,but we don't want to pass them to WrappedComponent pathIfNotLoggedIn,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,就像我们在路由负责时所做的那样.
这使您可以灵活地决定当中.请注意,将路径作为道具传递将优先于从组件传递到HOC.
现在都在一起了
这里有一个片段,结合了上面的所有概念,最终对HOC采取了以下措施:
const privateRoute = ({ requireLoggedIn = false,requireWaitlisted = false,pathIfNotLoggedIn = '/login',pathIfNotOnboarded = '/onboarding',pathIfNotWaitlisted = '/waitlist' } = {}) => WrappedComponent => { class Private extends Component { componentDidMount() { const { userStatus: { isLoggedIn,isWaitlisted },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,...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,pathIfNotWaitlisted } Private.displayName = `Private(${ WrappedComponent.displayName || WrappedComponent.name || 'Component' })` return hoistNonReactStatics( graphql(gql` query ... `)(Private),WrappedComponent ) } export default privateRoute