如何使用Apollo Client React路由器根据用户状态实现私人路线和重定向



我正在使用React Router 4进行路由和Apollo客户端以进行数据获取&缓存。我需要根据以下标准实现私有路线和重定向解决方案:

  1. 允许用户看到的页面基于其用户状态,该页面可以从服务器中获取或从缓存中读取。用户状态本质上是我们用来了解用户在漏斗中的位置的一组标志。示例标志:isLoggedInisOnboardedisWaitlisted等。

  2. 如果用户的状态不允许他们在该页面上,则甚至不应开始渲染页面。例如,如果您不是isWaitlisted,则不应该看到候补名单页面。当用户不小心发现自己在这些页面上时,应将其重定向到适合其状态的页面。

  3. 重定向也应该是动态的。例如,说您在isLoggedIn之前尝试查看用户配置文件。然后,我们需要将您重定向到登录页面。但是,如果您是isLoggedIn,而不是isOnboarded,我们仍然不希望您看到您的个人资料。因此,我们想将您重定向到入职页。

  4. 所有这些都需要在路线级别上发生。页面本身不应意识到这些权限&重定向。

总而言之,我们需要一个给定用户状态数据的库,可以

  • 计算用户是否可以在某个页面上
  • 计算他们需要重定向到动态的地方
  • 在渲染任何页面之前先执行这些操作
  • 在路线级别上执行这些操作

我已经在一家通用库上工作,但是现在有其缺点。我正在寻求有关如何解决这个问题的意见,以及是否有建立模式来实现这一目标。

这是我当前的方法。这是不起作用的,因为getRedirectPath需求在OnboardingPage component中的数据。

另外,我不能将私有餐具包裹在可以注入计算重定向路径所需的道具的事件中,因为这不会让我用作开关REACT ROUTION ROUT ROUCT ROUCT ROUTION ROUD ROUD ROUD ROUTION ROUTION组件,因为它停止了路线。

<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} />

重定向逻辑

如何设置此部分取决于两个因素:

  1. 您如何确定用户是否已登录,入职,候补等等,等等
  2. 您想负责哪个组件重定向到。

处理用户状态

由于您正在使用Apollo,因此您可能只想使用graphql在HOC中获取该数据:

return graphql(gql`
  query ...
`)(Private)

然后,您可以修改Private组件以获取这些道具:

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函数中添加一些额外的参数,然后在导出MyRoute时将其传递。

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&amp;3

(这是我喜欢使用的方法(

您还可以让组件和路线选择谁负责。您只需要像让组件决定一样为路径添加privateRoute参数。然后将这些值用作您的defaultProps,就像我们在路线负责时所做的一样。

这使您可以灵活地决定自己。请注意,通过路线作为道具将优先于从组件传递到事件。

现在一起

这是一个结合上面的所有概念的摘要

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


我正在使用官方文档中建议的提升 - 非反应统计。

我的人体用来建立我的私人路线:

const renderMergedProps = (component, ...rest) => {
  const finalProps = Object.assign({}, ...rest);
  return React.createElement(component, finalProps);
};
const PrivateRoute = ({
  component, redirectTo, path, ...rest
}) => (
  <Route
    {...rest}
    render={routeProps =>
      (loggedIn() ? (
        renderMergedProps(component, routeProps, rest)
      ) : (
        <Redirect to={redirectTo} from={path} />
      ))
    }
  />
);

在这种情况下, loggedIn()是一个简单的功能,如果用户被记录(取决于您处理用户会话的方式(,则可以创建每个私人路由。

然后您 can 在交换机中使用它:

<Switch>
    <Route path="/login" name="Login" component={Login} />
    <PrivateRoute
       path="/"
       name="Home"
       component={App}
       redirectTo="/login"
     />
</Switch>

PrivateRoute中的所有子分量都将首先需要检查用户是否已登录。

最后一步是根据其所需状态嵌套路由。

我认为您需要稍微向下移动逻辑。类似:

<Route path="/onboarding" render={renderProps=>
   <CheckAuthorization authorized={OnBoardingPage} renderProps={renderProps} />
}/>

您将不得不使用apolloclient,而无需'react-graphql'hoc。
1.获取Apolloclient的实例
2.火灾查询
3.查询返回数据渲染加载时。
4.根据数据检查并授权路由。
5.返回适当的组件或重定向。

这可以通过以下方式完成:

import Loadable from 'react-loadable'
import client from '...your ApolloClient instance...'
const queryPromise = client.query({
        query: Storequery,
        variables: {
            name: context.params.sellername
        }
    })
const CheckedComponent = Loadable({
  loading: LoadingComponent,
  loader: () => new Promise((resolve)=>{
       queryPromise.then(response=>{
         /*
           check response data and resolve appropriate component.
           if matching error return redirect. */
           if(response.data.userStatus.isLoggedIn){
            resolve(ComponentToBeRendered)
           }else{
             resolve(<Redirect to={somePath}/>)
           }
       })
   }),
}) 
<Route path="/onboarding" component={CheckedComponent} />

相关的API参考:https://www.apollographql.com/docs/react/reference/index.html

如果您正在使用Apollo React客户端,您也可以导入Query@apollo/components并在您的私人路线中使用它:

    <Query query={fetchUserInfoQuery(moreUserInfo)}>
      {({ loading, error, data: userInfo = {} }: any) => {
        const isNotAuthenticated = !loading && (isEmpty(userInfo) || !userInfo.whoAmI);
        if (isNotAuthenticated || error) {
          return <Redirect to={RoutesPaths.Login} />;
        }
        const { whoAmI } = userInfo;
        return <Component user={whoAmI} {...renderProps} />;
      }}
    </Query>

isEmpty只是检查给定对象是否为空:

const isEmpty = (object: any) => object && Object.keys(object).length === 0

最新更新