ReactRouterをネストさせて共通パーツの再描画を防ぐ(v6対応)

# 実現したいこと
ReactRouterでページのルーティングを実装しているが、
ルーティングする度にサイトのヘッダー/フッター/サイドナビなど共通パーツまで
各ページで描画していて全然SPAではなかった

共通パーツはルーティングが走っても再描画されないようにしたい

## 2021/12追記
react-router-dom v6でこの辺の実装方法が変わったので追記
変更点は[公式のv5->v6アップグレードガイド](https://reactrouter.com/docs/en/v6/upgrading/v5)だったり
[v6版チュートリアル/nested-routes](https://reactrouter.com/docs/en/v6/getting-started/tutorial#nested-routes)を参考にすると理解しやすいです


# 環境
React v17.0.1

# やったこと
## はじめる前

index.js

```tsx

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Switch>
        <Route exact path="/aaa/page1">
          <Page1 />
        </Route>
        <Route exact path="/bbb/page2">
          <Page2 />
        </Route>
        <Route exact path="/bbb/page3">
          <Page3 />
        </Route>
      </Switch>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

```



pages.tsx

```tsx
export function Page1() {
  return (
    <>
     <Header />
     <p>Page1</p>
     <Footer />
    </>
  );
}


export function Page2() {
  return (
    <>
     <Header />
     <SideNav />
     <p>Page2</p>
     <Footer />
    </>
  );
}


export function Page3() {
  return (
    <>
     <Header />
     <SideNav />
     <p>Page3</p>
     <Footer />
    </>
  );
}
```

上記のようなページを３つ持つサイトで以下の特徴を持ちます

- どのページもHeaderとFooterのコンポーネントを持つ
- /bbb/* のページ(Page2, Page3)は更に\<SideNav>コンポーネントも共通で持つ

これらの共通的な3つのコンポーネントを再描画しないようなRouterを作っていきたい


## 形だけでもRouterをネストさせる
まず、形の上でもRouterをネストさせてみる
参考にするのは [ReactRouterの公式Doc](https://reactrouter.com/web/example/nesting)

exampleのコードを見ると\<Switch>で分岐させた各Routeのchildren要素の中で更に<Switch>を使ってRouteの分岐をさせている
真似して書いてみると以下のように

index.js

```tsx

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Switch>
        <Route exact path="/aaa/page1">
          <Page1 />
        </Route>

        {/*今回変更した部分*/}
        <Route exact path="/bbb">
          <Switch>      
            <Route exact path="/bbb/page2">
              <Page2 />
            </Route>
            <Route exact path="/bbb/page3">
              <Page3 />
            </Route>
          </Switch>
        </Route>

      </Switch>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

```

まず `/bbb` でRouteを分岐させることでPage2とPage3をまとめられた
まだ、共通化はしていないので動作としては変わっていない


## 共通部分をRouterの外に出す
Page2とPage3を比べると差分は\<p>要素のみに見える(残りは共通)
そこでRouterを使って描画する部分を\<p>要素のみにする

pages.tsx

```tsx
export function Page1() {
  return (
    <>
     <Header />
     <p>Page1</p>
     <Footer />
    </>
  );
}


export function Page2() {
  return (
    <>
     <p>Page2</p>
    </>
  );
}


export function Page3() {
  return (
    <>
     <p>Page3</p>
    </>
  );
}
```

そして共通化させたい\<Header>/\<Footer>/\<SideNav>コンポーネントは
Page2とPage3を分岐させるRouterの上位に配置する

index.js

```tsx

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Switch>
        <Route exact path="/aaa/page1">
          <Page1 />
        </Route>

        {/*今回変更した部分*/}
        <Route exact path="/bbb">
          <Header />
          <SideNav />
          <Switch>      
            <Route exact path="/bbb/page2">
              <Page2 />
            </Route>
            <Route exact path="/bbb/page3">
              <Page3 />
            </Route>
          </Switch>
          <Footer />
        </Route>

      </Switch>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

```

これで `/bbb` 以下のページ遷移(Page2<>Page3)では共通化した
\<Header>/\<Footer>/\<SideNav>コンポーネントは再描画されなくなった



最後に\<Header>/\<Footer>/コンポーネントはPage1でも共通なので更に共通化させる


index.js

```tsx

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Header />
      <Switch>
        <Route exact path="/aaa/page1">
          <Page1 />
        </Route>
        <Route exact path="/bbb">
          <SideNav />
          <Switch>      
            <Route exact path="/bbb/page2">
              <Page2 />
            </Route>
            <Route exact path="/bbb/page3">
              <Page3 />
            </Route>
          </Switch>
        </Route>
      </Switch>
      <Footer />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

```

# react-routerv6に対応する
上記記法はv5のもので2021/12現在最新のreact router v6では記法が大きく変わっています
今回の書き方に影響するものだけまとめると

- \<Switch\>を使わなくても\<Route\>をネストできるようになった
- ネストされた\<Route\>では親\<Route\>からの相対パスでpathを表現する
- \<Route\>に対応するコンポーネントはelement propsで記述する
- ネスト親のコンポーネント内にネスト子のコンポーネントを表現する\<Outlet\>コンポーネントが追加

詳細は[v6版チュートリアル/nested-routes](https://reactrouter.com/docs/en/v6/getting-started/tutorial#nested-routes)を見ていただくとしてindex.jsとpaes.tsxがどう変わるかだけ

index.js

```tsx

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Route path="/" element={<PageFrame />}>
        <Route path="aaa/page1" element={<Page1 />} />
        <Route path="bbb/" element={<WithSideNav />}>
          <Route path="page2" element={<Page2 />} />
          <Route path="page3" element={<Page3 />} />
        </Route>
      </Route>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

```

pages.tsx

```tsx
export function PageFrame() {
  return (
    <>
     <Header />
     <Outlet />
     <Footer />
    </>
  );
}

export function WithSideNav() {
  return (
    <>
     <SadeNav />
     <Outlet />
    </>
  );
}

export function Page1() {
  return (
    <>
     <Header />
     <p>Page1</p>
     <Footer />
    </>
  );
}


export function Page2() {
  return (
    <>
     <p>Page2</p>
    </>
  );
}


export function Page3() {
  return (
    <>
     <p>Page3</p>
    </>
  );
}
```

Routerのネストに必要な要素が少なくなり
記述コスト、視認性が圧倒的に良くなっている。　これが公式サポートの力...
各コンポーネント側もネスト子要素をchileden -> Outletと特別扱いすることで
より取り回しがしやすくなったのではないかと


# 完成
当初やりたかったことは実装できた
ほとんど公式チュートリアルにあった通りだが、実際に使ってみた方が理解しやすいなぁ

個人的にはRouterのchildren要素はスッキリさせたいので`/bbb`以下のchildren要素をコンポーネント化するかと思う