Reactルーター4をルーティングに使用し、Apolloクライアントをデータのフェッチとキャッシュに使用しています。次の基準に基づいてPrivateRouteとリダイレクトソリューションを実装する必要があります。
ユーザーに表示を許可するページは、サーバーからフェッチしたり、キャッシュから読み取ったりできるユーザーステータスに基づいています。ユーザーステータスは基本的に、ユーザーが目標到達プロセスのどこにいるかを理解するために使用する一連のフラグです。フラグの例:isLoggedIn
、isOnboarded
、isWaitlisted
など。
ユーザーのステータスでページへの表示が許可されていない場合でも、ページのレンダリングを開始することはできません。たとえば、isWaitlisted
でない場合、順番待ちリストページは表示されません。ユーザーが誤ってこれらのページにアクセスした場合は、自分のステータスに適したページにリダイレクトする必要があります。
リダイレクトも動的である必要があります。たとえば、isLoggedIn
になる前にユーザープロファイルを表示しようとしたとします。次に、ログインページにリダイレクトする必要があります。ただし、あなたがisLoggedIn
であるがisOnboarded
でない場合でも、プロフィールを表示することは望ましくありません。そのため、オンボーディングページにリダイレクトします。
これはすべて、ルートレベルで行う必要があります。ページ自体は、これらの権限とリダイレクトを認識しないようにしておく必要があります。
結論として、ユーザーステータスデータを与えられたライブラリが必要です。
私はすでに汎用ライブラリに取り組んでいますが、現在は欠点があります。この問題にどのように取り組むべきか、そしてこの目標を達成するための確立されたパターンがあるかどうかについて意見を求めています。
これが私の現在のアプローチです。 getRedirectPath
が必要とするデータがOnboardingPage component
にあるため、これは機能していません。
また、リダイレクトパスの計算に必要な小道具を挿入できるHOCでPrivateRouteをラップすることはできません。これは、スイッチ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}
/>
すべてのページでこのロジックを処理する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
公式ドキュメント で提案されているように hoist-non-react-statics を使用しています。
ロジックを少し下げる必要があると思います。何かのようなもの:
<Route path="/onboarding" render={renderProps=>
<CheckAuthorization authorized={OnBoardingPage} renderProps={renderProps} />
}/>
私は個人的に次のようなプライベートルートを構築するために使用します:
_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を返す単純な関数です(ユーザーセッションの処理方法によって異なります)。このように、各プライベートルートを作成できます。
次に、canスイッチで使用します:
_<Switch>
<Route path="/login" name="Login" component={Login} />
<PrivateRoute
path="/"
name="Home"
component={App}
redirectTo="/login"
/>
</Switch>
_
このPrivateRoute
からのすべてのサブルートは、最初にユーザーがログインしているかどうかを確認する必要があります。
最後のステップは、必要なステータスに従ってルートをネストすることです。
'react-graphql'HOCなしでApolloClientを使用する必要があります。
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