create-react-app で作った Reactプロジェクト でページ遷移する（react-router-dom）

React でページ遷移の導入として `react-router-dom`(v6.16.0) を使ってやってみる
SPAとはいえページがあることで恩恵を受けることも結構あるのでやってみる
package.json に `homepage` 設定した時にちょっとはまった。。。
`process.env.PUBLIC_URL` を使うことで解決したのでそのあたりをメモしてます 
ベースとして `create-react-app` から作ったプロジェクトから不要ファイル削除したやつを使ってます
やることとしては大雑把にこんな感じ

- Reactプロジェクト作成 Win11
- Reactプロジェクトから不要ファイル削除（./src直下削除）
- Reactプロジェクトでページ遷移（react-router-dom v6.16.0使用）
  - react-router-dom インストール
  - ページのディレクトリ作成
  - ページ遷移の実装
  - ページ遷移の実装（コンポーネントにまとめる）
  - ページリンクのスタイル調整

あと最終的に出来上がるやつはここにおいてます
https://github.com/sueasen/my-app-react-router-dom

# React プロジェクト作成 Win11

こちらのページ参考、Node.jsインストールからやってます

https://qiita.com/sueasen/items/5aaac3dd4052de82c2f9

# React プロジェクトから不要ファイル削除（./src直下削除）

こちらのページ参考、初期化して `index.js` と `App.js` に簡単に分けてる感じです 

https://qiita.com/sueasen/items/99b25098823d966f1135

# Reactプロジェクトでページ遷移（react-router-dom v6.16.0使用）

## react-router-dom インストール

以下のコマンドを実行するだけ
```powershell
npm i react-router-dom
```

## ページのディレクトリ作成

`pages` ディレクトリを `src`直下に作成する

## ページ遷移の実装

ページ遷移の実装の流れとしては以下のステップを踏む
- 遷移用ページのコンポーネント作成
- `BrowserRouter` よりページ情報定義・表示
- ページ遷移のリンク追加

### 遷移用ページのコンポーネント作成

`pages`直下に簡単に文字を表示するだけのページを3つ(`Home`, `Page1`, `Page2`)作成する
`Page2` は `props`(引数) もらうようにしておく
```jsx:Home.js
const Home = () => {
  return (
    <>
      <p>Home</p>
    </>
  );
};
export default Home;
```
```jsx:Page1.js
const Page1 = () => {
  return (
    <>
      <p>Page1</p>
    </>
  );
};
export default Page1;
```
```jsx:Page2.js
const Page2 = (props) => {
  return (
    <>
      <p>Page2 {props.text}</p>
    </>
  );
};
export default Page2;
```

### `BrowserRouter` よりページ情報定義・表示

App.js に `BrowserRouter` 使ってページ情報を定義・表示するために以下をやる
- BrowserRouter, Routes, Route を react-router-dom からインポート
- ページ用コンポーネントをインポート
- BrowserRouter の basename に `process.env.PUBLIC_URL` を設定
  - `process.env.PUBLIC_URL` を設定すると `homepage` 設定した時などパスが狂わなくなる
- Route に各々の `path`, `element`(コンポーネント) を設定

```jsx:App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Page1 from './pages/Page1';
import Page2 from './pages/Page2';

const App = () => {
  return (
    <>
      <h1>Hello World</h1>
      <p>App.jsx</p>
      {/* BrowserRouter, Routes, Route の定義、path に一致する element を表示 */}
      <BrowserRouter basename={process.env.PUBLIC_URL}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/page1" element={<Page1 />} />
          <Route path="/page2" element={<Page2 text="text sample" />} />
        </Routes>
      </BrowserRouter>
    </>
  );
};
export default App;
```

実行するとURLに応じてページが切り替わればO
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/62b028d4-3744-6876-2f60-2e5fc411408b.png)

### ページ遷移のリンク追加

ページ遷移用のリンクを追加すればOK
設定するリンクには basename に設定した `process.env.PUBLIC_URL` を付けるとパスが狂わなくなる

```jsx:App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Page1 from './pages/Page1';
import Page2 from './pages/Page2';

const App = () => {
  return (
    <>
      <h1>Hello World</h1>
      <p>App.jsx</p>
      {/* 以下リンクを追加 */}
      <ul>
        <li>
          <a href={`${process.env.PUBLIC_URL}/`}>Home</a>
        </li>
        <li>
          <a href={`${process.env.PUBLIC_URL}/page1`}>Page1</a>
        </li>
        <li>
          <a href={`${process.env.PUBLIC_URL}/page2`}>Page2</a>
        </li>
      </ul>
      {/* BrowserRouter, Routes, Route を定義 */}
      <BrowserRouter basename={process.env.PUBLIC_URL}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/page1" element={<Page1 />} />
          <Route path="/page2" element={<Page2 text="text sample" />} />
        </Routes>
      </BrowserRouter>
    </>
  );
};

export default App;

```

実行してリンクに応じてページが切り替わればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/db09e0b1-24e6-a5c6-1a7a-d27a7ec4721d.png)

## ページ遷移の実装（コンポーネントにまとめる）

ページ遷移の実装をコンポーネントにまとめてみる
ページ遷移に関する情報を変数定義してそれを渡して `BrowserRouter`, ページ遷移リンクを返すやつを作る
もっと良い方法ありそうだが定義したページ情報を渡せばできる形にしてみる

### ページ遷移リンクのコンポーネント作成

ページ遷移リンク を返すコンポーネントを `pages` 直下に `AppRouter.js` として作る

```jsx:AppRouter.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// pages は [{key:key1, path:path1, element:element1}, ...] を受け取る
export const appRouter = (pages) => {
  // BrowserRouter 返す処理
  const router = () => {
    return (
      <>
        <BrowserRouter basename={process.env.PUBLIC_URL}>
          <Routes>
            {pages.map((p) => (
              <Route key={p.key} path={p.path} element={p.element} />
            ))}
          </Routes>
        </BrowserRouter>
      </>
    );
  };
  // ページ遷移リンク 返す処理
  const links = () => (
    <ul>
      {pages.map((p) => (
        <li key={p.key}>
          <a href={`${process.env.PUBLIC_URL}${p.path}`}>{p.key}</a>
        </li>
      ))}
    </ul>
  );
  // BrowserRouter, ページ遷移リンク の定義を返す
  return {
    browserRouter: router(),
    navbarLink: links(),
  };
};
```

### App.js から AppRouer.js を使って実装

インポートして pages を定義して AppRouer に設定する
後は `browserRouter`, `navbarLink` を使って書き換える

```jsx:App.js
import { appRouter } from './pages/AppRouter';
import Home from './pages/Home';
import Page1 from './pages/Page1';
import Page2 from './pages/Page2';

// ページ情報を定義して appRouter に設定
const pages = [
  { key: 'Home', path: '/', element: <Home /> },
  { key: 'Page1', path: '/page1', element: <Page1 /> },
  { key: 'Page2', path: '/page2', element: <Page2 text="text sample" /> },
];
const router = appRouter(pages);

const App = () => {
  return (
    <>
      <h1>Hello World</h1>
      <p>App.jsx</p>
      {/* appRouter の navbarLink に書き換え*/}
      {router.navbarLink}
      {/* appRouter の browserRouter に書き換え*/}
      {router.browserRouter}
    </>
  );
};

export default App;
```

実行してページ遷移が出来ればOK！！

## ページリンクのスタイル調整

メニューリンクなどにスタイルをあてる
- `App.css` : 新規作成、スタイルを定義
- `App.js` : インポート追加 `App.css`
- `AppRouter.js` : タグやクラス属性を追加

```css:App.css
body {
    margin: 0;
    font-family: "Futura", Helvetica, sans-serif;
}

/* Navbar & Navmenu color */
:root {
    --background-navbar: rgba(55, 55, 55, 0.98);
    --height-navbar: 52px;
}

.header {
    background: var(--background-navbar);
    position: fixed;
    width: 100%;
    height: var(--height-navbar);
    z-index: 3000;    
}

/* Nav items */
.menu {
    list-style: none;
    position: absolute;
    width: 100%;
    height: auto;
    top: 0;
    margin-top: var(--height-navbar);
    padding: 0 0 10px 0;
    clear: both;
    background: var(--background-navbar);
    transition: 0.3192s cubic-bezier(0.04, 0.04, 0.12, 0.96) 0.1008s;
    transform: scale(1, 0);
    transform-origin: top;
}

/* Hamburger menu button */
.menu-btn:checked~.menu {
    transform: scale(1, 1);
    transform-origin: top;
    transition: 0.3192s cubic-bezier(0.04, 0.04, 0.12, 0.96) 0.1008s;
}

/* Hamburger menbu text */
.menu a {
    text-decoration: none;
    font-weight: 500;
    letter-spacing: 2px;
    font-size: 16px;
    text-transform: capitalize;
    color: #ddd;
    opacity: 0;
    transition: 0.5s;
}

.menu li {
    border-top: 1px solid rgb(75, 75, 75);
    padding: 15px 0;
    margin: 0 54px;
    opacity: 0;
    transition: 0.5s;
}

.menu-btn:checked~.menu a,
.menu-btn:checked~.menu li {
    opacity: 1;
    transition: 0.3192s cubic-bezier(0.04, 0.04, 0.12, 0.96) 0.2s;
}

.menu-btn {
    display: none;
}

.menu-icon {
    display: inline-block;
    position: relative;
    cursor: pointer;
    padding: 24px 14px;
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

.navicon {
    background: #ddd;
    display: block;
    height: 3px;
    width: 26px;
    position: relative;
    transition: 0.3192s cubic-bezier(0.04, 0.04, 0.12, 0.96) 0.1008s;
}

.navicon:before,
.navicon:after {
    content: "";
    display: block;
    height: 100%;
    width: 100%;
    position: absolute;
    background: #ddd;
    transition: 0.3192s cubic-bezier(0.04, 0.04, 0.12, 0.96) 0.1008s;
}

.navicon:before {
    top: 9px;
}

.navicon:after {
    bottom: 9px;
}

/* Hamburger Menu Animation Start */
.menu-btn:checked~.menu-icon .navicon:before {
    transform: rotate(-45deg);
}

.menu-btn:checked~.menu-icon .navicon:after {
    transform: rotate(45deg);
}

.menu-btn:checked~.menu-icon:not(.steps) .navicon:before {
    top: 0;
}

.menu-btn:checked~.menu-icon:not(.steps) .navicon:after {
    bottom: 0;
}

.menu-btn:checked~.menu-icon .navicon {
    background: rgba(0, 0, 0, 0);
    transition: 0.2192s cubic-bezier(0.04, 0.04, 0.12, 0.96) 0.1008s;
}

/* Hamburger Menu Animation End */

/* Navbar Container */
.navtext-container {
    width: 100%;
    height: var(--height-navbar);
    position: absolute;
    box-sizing: border-box;
    display: flex;
    justify-content: center;
    align-items: center;
}

/* Navbar Text */
.navtext {
    position: absolute;
    text-transform: uppercase;
    color: #ddd;
    letter-spacing: 4px;
    font-size: 20px;
}

main {
    padding-top: var(--height-navbar);
}
```

```jsx:App.js
import { appRouter } from './pages/AppRouter';
import Home from './pages/Home';
import Page1 from './pages/Page1';
import Page2 from './pages/Page2';
// css 追加
import './App.css';

// ページ情報を定義して appRouter に設定
const pages = [
  { key: 'Home', path: '/', element: <Home /> },
  { key: 'Page1', path: '/page1', element: <Page1 /> },
  { key: 'Page2', path: '/page2', element: <Page2 text="text sample" /> },
];
const router = appRouter(pages);

const App = () => {
  return (
    <>
      {router.navbarLink}
      {/* 位置調整で main で括る */}
      <main>
        <h1>Hello World</h1>
        <p>App.jsx</p>
        {router.browserRouter}
      </main>
    </>
  );
};

export default App;
```

```jsx:AppRouter.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';

export const appRouter = (pages) => {
  const router = () => {
    return (
      <>
        <BrowserRouter basename={process.env.PUBLIC_URL}>
          <Routes>
            {pages.map((p) => (
              <Route key={p.key} path={p.path} element={p.element} />
            ))}
          </Routes>
        </BrowserRouter>
      </>
    );
  };
  // タグ, クラスなど追加
  const links = () => (
    <header className="header">
      <div className="navtext-container">
        <div className="navtext">title</div>
      </div>
      <input type="checkbox" className="menu-btn" id="menu-btn" />
      <label htmlFor="menu-btn" className="menu-icon">
        <span className="navicon"></span>
      </label>
      <ul className="menu">
        {pages.map((p) => (
          <li key={p.key}>
            <a href={`${process.env.PUBLIC_URL}${p.path}`}>{p.key}</a>
          </li>
        ))}
      </ul>
    </header>
  );
  return {
    browserRouter: router(),
    navbarLink: links(),
  };
};
```

こんな感じで表示されればOK

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/fc069986-0363-b8fb-5c82-2462b5295d32.png)