[React,TypeScript,react-router] しっかり型定義して、未認証ユーザーはリダイレクトするPrivateRouteをwithRouterで作る

# はじめに
jsで書かれた少し古めの技術書を写経する際に、withRouterを使った実装をTypeScriptで置換したのでメモ。
withRouterではなくuseHisotryを使った方法は記事末尾に記載。
react-routerについては最新のv6による記法ではないです。


# この記事でできるようになること
- react-routerでTypeScriptで型定義をした上でwithRouterを使えるようになる
- Routeコンポーネントをラップして認証していないユーザーを特定のページへリダイレクトするPrivateRouteを作成する。

イメージ

```tsx
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`が用意されているのでそれを利用する。

```typescript
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;
}
```
[こちらの記事](https://qiita.com/taneba/items/b50078e1ac9d1971d413)が参考になった。

### ラップするRouteコンポーネントに渡すprops群
Routeコンポーネントの定義をあさって、props用の型`RouteProps`を発見したのでこれを使う。

```typescript
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を渡すだけなので以下のようにしても良い。

```typescript
{path:string,component:React.ComponentType<any>}
```

## 作成

認証処理の具体的な内容は使っている認証基盤によっても異なるので今回は割愛。

```tsx
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を使う場合は以下。

```tsx
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}
        />
    );

});

```

# 参考
- [React Router with TypeScriptでちゃんと型をつける](https://qiita.com/taneba/items/b50078e1ac9d1971d413)
- [JavascriptEverywhere(今回読んだ技術書)](https://www.oreilly.com/library/view/javascript-everywhere/9781492046974/)