AnA*_*ice 11 apollo reactjs react-router react-apollo apollo-client
我使用React Router 4进行路由,使用Apollo Client进行数据获取和缓存.我需要根据以下标准实施PrivateRoute和重定向解决方案:
允许用户查看的页面基于用户状态,可以从服务器获取,也可以从缓存中读取.用户状态本质上是一组标志,用于了解用户在我们的渠道中的位置.例如国旗:isLoggedIn,isOnboarded,isWaitlisted等.
如果用户的状态不允许他们在该页面上,则甚至不应该开始呈现页面.例如,如果不是isWaitlisted,则不应该看到等待列表页面.当用户意外地发现自己在这些页面上时,应将其重定向到适合其状态的页面.
重定向也应该是动态的.例如,假设您在尝试之前查看用户个人资料isLoggedIn.然后我们需要将您重定向到登录页面.但是,如果您isLoggedIn不是isOnboarded,我们仍然不希望您看到您的个人资料.因此,我们希望将您重定向到新手入门页面.
所有这些都需要在路由级别上进行.页面本身应该不知道这些权限和重定向.
总之,我们需要一个给出用户状态数据的库,可以
我已经在开发一个通用库,但它现在有它的缺点.我正在寻求关于如何解决这个问题的意见,以及是否有既定的模式来实现这一目标.
这是我目前的做法.这不起作用,因为getRedirectPath需要的数据在OnboardingPage component.
此外,我不能将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}
/>
Run Code Online (Sandbox Code Playgroud)
我会创建一个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
Run Code Online (Sandbox Code Playgroud)
然后,您只需要更改导出路线的方式:
export default privateRoute({ requireLoggedIn: true })(MyRoute);
Run Code Online (Sandbox Code Playgroud)
你可以像在今天的react-router中一样使用那条路线:
<Route path="/" component={MyPrivateRoute} />
Run Code Online (Sandbox Code Playgroud)
如何设置此部分取决于几个因素:
既然你正在使用Apollo,你可能只想graphql用来获取你的HOC中的数据:
return graphql(gql`
query ...
`)(Private)
Run Code Online (Sandbox Code Playgroud)
然后你可以修改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} />
)
}
}
Run Code Online (Sandbox Code Playgroud)
您可以使用几个不同的地方来处理这个问题.
如果用户未登录,则始终要路由到/login?return=${currentRoute}.
在这种情况下,您可以在您的硬盘中对这些路线进行硬编码componentDidMount.完成.
如果希望MyRoute组件确定路径,可以在privateRoute函数中添加一些额外的参数,然后在导出时传入它们MyRoute.
const privateRoute = ({
requireLoggedIn = false,
pathIfNotLoggedIn = '/a/sensible/default',
// ...
}) // ...
Run Code Online (Sandbox Code Playgroud)
然后,如果要覆盖默认路径,则将导出更改为:
export default privateRoute({
requireLoggedIn: true,
pathIfNotLoggedIn: '/a/specific/page'
})(MyRoute)
Run Code Online (Sandbox Code Playgroud)
如果您希望能够从路由传递路径,您将需要接收这些路径 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'
}
Run Code Online (Sandbox Code Playgroud)
然后您的路线可以重写为:
<Route path="/" render={props => <MyPrivateComponent {...props} pathIfNotLoggedIn="/a/specific/path" />} />
Run Code Online (Sandbox Code Playgroud)
(这是我喜欢使用的方法)
您还可以让组件和路径选择负责人.您只需要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'
})`
hoistNonReactStatics(Private, WrappedComponent)
return graphql(gql`
query ...
`)(Private)
}
export default privateRouteRun Code Online (Sandbox Code Playgroud)
我正在使用官方文档中建议的hoist-non-react-statics.