我使用React Router 4进行路由,使用Apollo Client进行数据获取&缓存。我需要根据以下标准实施PrivateRoute和重定向解决方案:
允许用户查看的页面基于用户状态,可以从服务器获取,也可以从缓存中读取。用户状态本质上是一组标志,用于了解用户在我们的渠道中的位置。示例标记:isLoggedIn
,isOnboarded
,isWaitlisted
等。
如果用户的状态不允许他们在该页面上,则不会开始呈现任何页面。例如,如果您不是isWaitlisted
,则不应该看到等候名单页面。当用户意外地发现自己在这些页面上时,应将他们重定向到适合其状态的页面。
重定向也应该是动态的。例如,假设您在isLoggedIn
之前尝试查看用户个人资料。然后我们需要将您重定向到登录页面。但是,如果您是isLoggedIn
但不是isOnboarded
,我们仍然不希望您看到自己的个人资料。因此,我们希望将您重定向到新手入职页面。
所有这些都需要在路由级别上进行。页面本身应该不知道这些权限&重定向。
总之,我们需要一个提供用户状态数据的库,可以
我已经在开发一个通用库,但它现在有它的缺点。我正在寻求关于如何解决这个问题的意见,以及是否有既定的模式来实现这一目标。
这是我目前的做法。这不起作用,因为getRedirectPath
所需的数据位于OnboardingPage component
。
另外,我不能用HOC包装PrivateRoute,它可以注入计算重定向路径所需的道具,因为这不会让我将它用作Switch React Router组件的子节点,因为它不再是路由。
<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}
/>
答案 0 :(得分:9)
我会创建一个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
)
然后你可以修改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 = ({
requireLogedIn = 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" />} />
(这是我喜欢使用的方法)
您也可以让组件和路线选择负责人。您只需要为路径添加privateRoute
参数,就像我们让组件决定一样。然后将这些值用作defaultProps
,就像我们在路线负责时所做的那样。
这使您可以灵活地决定当中。请注意,将路径作为道具传递将优先于从组件传递到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'
})`
return hoistNonReactStatics(
graphql(gql`
query ...
`)(Private),
WrappedComponent
)
}
export default privateRoute
答案 1 :(得分:1)
我认为你需要稍微改变你的逻辑。类似的东西:
<Route path="/onboarding" render={renderProps=>
<CheckAuthorization authorized={OnBoardingPage} renderProps={renderProps} />
}/>
答案 2 :(得分:1)
我个人用来建立我的私人路线:
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()
是一个简单的函数,如果记录了用户,则返回true(取决于您处理用户会话的方式),您可以像这样创建每个私有路由。
然后你可以在Switch中使用它:
<Switch>
<Route path="/login" name="Login" component={Login} />
<PrivateRoute
path="/"
name="Home"
component={App}
redirectTo="/login"
/>
</Switch>
此PrivateRoute
的所有子路由首先需要检查用户是否已登录。
最后一步是根据所需状态嵌套路线。
答案 3 :(得分:0)
你必须使用没有'react-graphql'HOC的ApolloClient
1.获取ApolloClient的实例
2.火灾查询
3. Query返回数据渲染加载..
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
答案 4 :(得分:0)
如果您使用的是apollo react client,您还可以导入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