はじめに
jsで書かれた少し古めの技術書を写経する際に、withRouterを使った実装をTypeScriptで置換したのでメモ。
withRouterではなくuseHisotryを使った方法は記事末尾に記載。
react-routerについては最新のv6による記法ではないです。
この記事でできるようになること
- react-routerでTypeScriptで型定義をした上でwithRouterを使えるようになる
- Routeコンポーネントをラップして認証していないユーザーを特定のページへリダイレクトするPrivateRouteを作成する。
イメージ
const App = () => {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/signup" component={SignUp} />
<Route path="/signin" component={SignIn} />
//↓未認証ユーザーは別ルートへリダイレクトする
<PrivateRoute path="/new" component={NewPost} />
</Switch>
</Router>
);
};
withRouterとは?
react-routerが提供しているHOC(高階コンポーネント)。
withRouter(Hoge)
のようにコンポーネントを包むことで、Route
コンポーネントが受け取るようなhisotry
やlocation
などをpropsとしてラップしたコンポーネント(Hoge)に渡すことができる。
認証処理に失敗したときにリダイレクトを行うにはhistory
が必要なので今回利用している。
実は、同じくreact-routerが提供しているuseHisotryを使えばwithRouterを使わずともhistoryにアクセスできるが、今回は技術書コードに型を付けたかっただけなので選択肢として選ばなかった。
コンポーネントの作成
必要なpropsの洗い出し
今回作成するPrivateRouteコンポーネントが持つべきpropsは以下
- 認証に用いるwithRouterから渡されるprops群
- hisotry,location etc
- ラップするRouteコンポーネントに渡すprops群
- path, component etc
型を探す
withRouterで渡されるprops群
react-router-domの中にRouteComponentProps
が用意されているのでそれを利用する。
export interface RouteComponentProps<
Params extends { [K in keyof Params]?: string } = {},
C extends StaticContext = StaticContext,
S = H.LocationState
> {
history: H.History<S>;
location: H.Location<S>;
match: match<Params>;
staticContext?: C | undefined;
}
こちらの記事が参考になった。
ラップするRouteコンポーネントに渡すprops群
Routeコンポーネントの定義をあさって、props用の型RouteProps
を発見したのでこれを使う。
export interface RouteProps<
Path extends string = string,
Params extends { [K: string]: string | undefined } = ExtractRouteParams<Path, string>
> {
location?: H.Location | undefined;
component?: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any> | undefined;
render?: ((props: RouteComponentProps<Params>) => React.ReactNode) | undefined;
children?: ((props: RouteChildrenProps<Params>) => React.ReactNode) | React.ReactNode | undefined;
path?: Path | readonly Path[] | undefined;
exact?: boolean | undefined;
sensitive?: boolean | undefined;
strict?: boolean | undefined;
}
今回はpath,componentを渡すだけなので以下のようにしても良い。
{path:string,component:React.ComponentType<any>}
作成
認証処理の具体的な内容は使っている認証基盤によっても異なるので今回は割愛。
type PrivateRouteProps = RouteComponentProps & RouteProps
// あるいは
// PrivateRouteProps = RouteComponentProps & {path:string,component:React.ComponentType<any>}
const PrivateRoute = withRouter(({history,path,component}:PrivateRouteProps) => {
// ここに認証処理を書く
// 仮に認証の成否をisAuthenticatedに入れるとすると以下のように書くことができる
//
if(!isAuthenticated) {
history.push('/signIn');//signInページにリダイレクトする
}
return (
<Route
path={path}
component={component}
/>
);
});
おまけ
useHisotryを使う場合は以下。
import {useHisotry} from 'react-router-dom';
type PrivateRouteProps = RouteProps
const PrivateRoute = ({path,component}:PrivateRouteProps) => {
// ここに認証処理を書く
// 仮に認証の成否をisAuthenticatedに入れるとすると以下のように書くことができる
//
if(!isAuthenticated) {
history.push('/signIn');//signInページにリダイレクトする
}
return (
<Route
path={path}
component={component}
/>
);
});