react-router@v6でlazy, nested routesを実施する

# react-router@v6でlazy,nested-routesを実現する

## TL;DR


React Lazyを利用して、
`child-component`でnestedする場合のpathには、`path="child-nested/*"`を実施、<Routes />componentをparentに持つ。
`child-component`をlazyで利用する場合は、lazyの中に入れればOK。


## table of contents

0. 環境
0. childを遅延読み込みさせる
0. nested-childを遅延読み込みさせる

## 1. 環境

|package name| version |
|:---:|:---:|
| vite | 3.0.4 |
| react | 18.2.0 |
| react-dom | 18.2.0 |
| react-router | 6.3.0 |
| react-router-dom | 6.3.0 |

```App.tsx
export function App() {
    return (
        <div>
            <BrowserRouter>
                <nav>
                  <ul>
                    <li><Link to="/">root</Link></li>
                    <li><Link to="/child">child</Link></li>
                    <li><Link to="/child-nested">child nested root</Link></li>
                    <li><Link to="/child-nested/a">child nested a</Link></li>
                    <li><Link to="/child-nested/b">child nested b</Link></li>
                  </ul>
                </nav>
                <main>
                    <Route path="/" element={<p>root main</p>} />
                </main>
            </BrowserRouter>
        </div>
    )
}
```

```error-bondary.tsx
import { Component, ErrorInfo, ReactNode } from "react"

interface ErrorBoundaryState {
    hasError: boolean
}

interface ErrorBoundaryProps {
  children: ReactNode
}

export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: unknown) {
    return { hasError: true };
  }

  override componentDidCatch(error: unknown, errorInfo: ErrorInfo) {
      console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}
```

## 1. childを遅延読み込みさせる

childの遅延読み込みは、[React.lazy](https://reactjs.org/docs/code-splitting.html#reactlazy)を利用して、componentの呼び出しを実施するだけで遅延読み込みができます。

```child-page.tsx
export function ChildPage() {
    return (
        <p>child page</p>
    )
}

export default ChildPage
```

```App.tsx
// [Start]追記
const ChildPage = lazy(() => import('./child-page'))
// [End]追記

export function App() {
    return (
        <div>
            <BrowserRouter>
                <nav>
                  <ul>
                    <li><Link to="/">root</Link></li>
                    <li><Link to="/child">child</Link></li>
                    <li><Link to="/child-nested">child nested root</Link></li>
                    <li><Link to="/child-nested/a">child nested a</Link></li>
                    <li><Link to="/child-nested/b">child nested b</Link></li>
                  </ul>
                </nav>
                <main>
                    <Route path="/" element={<p>root main</p>} />
                    // [Start]追記
                    <Route path="child" element={
                        <ErrorBoundary>
                            <Suspense fallback={<p>Loading...</p>}>
                                <ChildPage />
                            </Suspense>
                        </ErrorBoundary>
                    } />
                    // [End]追記
                </main>
            </BrowserRouter>
        </div>
    )
}
```

React.lazyを利用しているため、<Suspense />を利用して読み込み中の表示を実施します。
また、例外処理を拾わないため、<ErrorBoundary />でError時間の処理を実施します。


## 2. nested-childを遅延読み込みさせる

child-componentがroutingを保持していない場合には、上記の対応だけでいいのですがroutingを分割したり実施する場合には、追加で対応が必要になります。
必要な項目としては、

1. ChildNesteComponentsのparentに<Routes />を定義する
1. lazyで読み込むComopnentのpathは、`FOO/*`の形式にする

```child-nested-page.tsx
import { Route, Routes } from "react-router-dom"

export function ChildNestedPage() {
    return (
        <Routes>
            <Route index element={<div>child nested root</div>} />
            <Route path="a" element={<div>child nested page a</div>} />
            <Route path="b" element={<div>child nested page b</div>} />
        </Routes>
    )
}

export default ChildNestedPage
```

```App.tsx
const ChildPage = lazy(() => import("./child-page"))
// [Start]追記
const ChildNestedPage = lazy(() => import("./child-nested-page"))
// [End]追記

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <nav>
          <ul>
            <li><Link to="/">root</Link></li>
            <li><Link to="/child">child</Link></li>
            <li><Link to="/child-nested">child nested root</Link></li>
            <li><Link to="/child-nested/a">child nested a</Link></li>
            <li><Link to="/child-nested/b">child nested b</Link></li>
          </ul>
        </nav>
        <Routes>
          <Route path="" element={<div>main</div>} />
          // [Start]追記
          /**
           * path="child-nested"の形だとroutingが読み込まれません
           * path="child-nested/*"にすると正常に読み込みがされる
          **/
          <Route path="child-nested/*" element={
            <ErrorBoundary>
              <Suspense fallback={<div>Loading...</div>}>
                <ChildNestedPage />
              </Suspense>
            </ErrorBoundary>
          } />
          // [End]追記
          <Route path="child" element={
            <ErrorBoundary>
              <Suspense fallback={<div>Loading...</div>}>
                <ChildPage />
              </Suspense>
            </ErrorBoundary>
          } />
        </Routes>
      </BrowserRouter>
    </div>
  )
}

export default App

```

Sampleも空になっているので、気が向いたら本家にPRだして、解答をいただきたいと思っています。

[Sample Repository](https://github.com/Ntakuya/react-recoil/tree/main/react-router-nested-lazy-example)