Ответ 1
Общий подход
Я бы создал 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);
и вы можете использовать этот маршрут так же, как вы используете его сегодня в реагирующем маршрутизаторе:
<Route path="/" component={MyPrivateRoute} />
Логика перенаправления
Как вы установите эту часть, зависит от нескольких факторов:
- Как вы определяете, вошел ли пользователь в систему, включен ли он, находится ли в списке ожидания и т.д.
- На какой компонент вы хотите отвечать, куда перенаправить.
Обработка статуса пользователя
Поскольку вы используете 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 & 3
(Это подход, который мне нравится использовать)
Вы также можете позволить компоненту и маршруту выбирать, кто несет ответственность. Вам просто нужно добавить параметры 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 privateRoute