※当サイトの記事には、広告・プロモーションが含まれます。

React Routerのルーティング定義を複数ファイルに分割したい

www.itmedia.co.jp

⇧ 日本語対応はまだと...、日本語は難しいんかね?

React Routerとは?

React RouterのGitHub のページによりますと、

github.com

React Router is a lightweight, fully-featured routing library for the React JavaScript library. React Router runs everywhere that React runs; on the web, on the server (using node.js), and on React Native.

https://github.com/remix-run/react-router

⇧ Reactが動いてる環境であれば、どこでも動く、軽量で十分な機能を備えたルーティングライブラリですと。

This repository is a monorepo containing the following packages:

https://github.com/remix-run/react-router

⇧ 3つのパッケージに分かれていて、用途毎に導入する感じになりますと。

「React Router」は、

medium.com

⇧ ReactのEcosystem的な位置付けらしいので、Reactのプロジェクトで必要があれば導入していく感じですかね?

Reactと関連して良く聞く、「Next.js」はと言うと、

fmontes.com

⇧ 上図のような関係のイメージらしい。

JavaSpring FrameworkSpring Frameworkにまつわる依存関係の関係と比べる感じで考えてみると、

Java JavaScript
Spring Framework React
Spring Web MVC React Router
Spring Boot Next.js

⇧ みたいな関係性のイメージになるんかな?

ちなみに、「React Router」を手掛けていた人たちが、

zenn.dev

⇧「Remix」っていうフレームワークを作ってるらしい、GitHubリポジトリがあったのはそういうことか。

React Routerのルーティング定義を複数ファイルに分割したい

だいぶ、話が脱線しましたが、ようやっと本題。

大規模開発とかになってくると、ページ数が膨大になってくると思うので、ルーティング定義とかを複数ファイルに分割したいこと、あるあるだと思うんですよね。

cliffzhao.hatenablog.com

⇧ 上記サイト様で解説してくれておりました。

ちなみに、

stackoverflow.com

⇧ 海外だと、複数ファイルに分割する発想がないのかな?

それとも、海外のシステムと日本のシステムの思想の違いとかあるんですかね?

でも、Amazonとかのサイト見てると、サイドバーのリンクでカテゴリとか複数あるから、ルーティング定義を複数ファイルに分割しておくのが良いような気がするから、複数ファイルに分割したくなるケースあると思うんだけど...

ちなみに、Amazonのサイトを「Wappalyzer」っていうChrome拡張機能で確認してみたところ、

⇧ Reactを使ってるっぽい。

のだけど、「React Developer Tool」って拡張機能で分析しようとしたらば、

⇧ 使ってないって言われる...

Amazonの実装を参考にさせてもらおうと思ったんだけど、残念...

2023年4月22日(土)追記:↓ ここから

どうやら、「React Developer Tool」でAmazonのページでReactが使われていないと判定されてしまったのは、

qiita.com

⇧ 上記サイト様の説明によると、Reactを使ってるページじゃなかっただけで、どこかのページでReactが使われているということになりそう。

どのページでReactが使われてるかを探してくれる便利な拡張機能はないのかしら...

2023年4月22日(土)追記:↑ ここまで

脱線しましたが、ルーティング定義を複数ファイルに分割するのを試してみました。

ちなみに、

profy.dev

  • The solution: use kebab-case for your file and folder names. For example:

    • Instead of MyComponent.js write my-component.js.
    • Instead of useMyHook.js write use-my-hook.js.

    This is what Next.js uses by default. Angular included it in its coding styleguide. I don't see a reason why not to use kebab-case but it might save you or a teammate of yours some headaches.

https://profy.dev/article/react-folder-structure

⇧ ファイル名、フォルダ名は、ケバブケースにしておいた方が良いんですかね?

まぁ、プロジェクトに合わせる感じなのかな?

ちなみに、ルーティング定義を階層構造にする方法とかは、

github.com

⇧ 公式のドキュメントでは、TODOになってしまっているそうです...

何て言うか、見切り発車過ぎる気が...現場が混乱するだけだから、ドキュメントは公開前に整備して欲しいかな...

qiita.com

⇧ 上記サイト様によりますと、β版の例はあるそうです、Reactも結構カオスな感じなのか...と言うか、ReactのEcosystemがカオスってことかね?

zenn.dev

⇧ <Outlet />ってのが曲者で、ルーティングするパスが階層構造になってる必要があるっぽい。

脱線しましたが、react-router-domについては、

ts0818.hatenablog.com

⇧ 前回の記事で、npm installしてますが、

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\package.json

{
  "name": "my-react-spa-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@types/react-router-dom": "^5.3.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.10.0"
  },
  "devDependencies": {
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@vitejs/plugin-react": "^3.1.0",
    "typescript": "^4.9.3",
    "vite": "^4.2.0"
  }
}

⇧ バージョン6.10.0を使っています。

いまいち、フロントエンドのディレクトリ構造が分からんのだけど、ルーティング定義を複数ファイルに分けたいので、複数ページを用意。

全体は、以下のような構成になりました。

C:.
│  .gitignore
│  index.html
│  package-lock.json
│  package.json
│  tsconfig.json
│  tsconfig.node.json
│  vite.config.ts
│  
├─node_modules
│  │  .package-lock.json
│  │  
│     ...省略
│      
│  ├─.bin
│  │
│     ...省略
│
│
├─public
│      vite.svg
│      
└─src
    │  App.css
    │  App.tsx
    │  index.css
    │  main.tsx
    │  vite-env.d.ts
    │  
    ├─assets
    │      react.svg
    │      
    ├─page
    │  │  404.tsx
    │  │  
    │  ├─about
    │  │      index.tsx
    │  │      
    │  ├─book
    │  │  │  index.tsx
    │  │  │  
    │  │  ├─children-book
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─commic
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─foreign-book
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─hard-cover
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─magazine
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─paper-back
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─picture-book
    │  │  │      index.tsx
    │  │  │      
    │  │  └─pocket-edition-book
    │  │          index.tsx
    │  │          
    │  ├─food-beverage-liqour
    │  │  │  index.tsx
    │  │  │  
    │  │  ├─food-beverage
    │  │  │      index.tsx
    │  │  │      
    │  │  └─liqour
    │  │      │  index.tsx
    │  │      │  
    │  │      ├─beer
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─chuhai-and-cocktail
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─low-malt-beer
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─non-alcoholic
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─sake
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─shouchu
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─western-liqours-and-liquers
    │  │      │      index.tsx
    │  │      │      
    │  │      └─wine
    │  │              index.tsx
    │  │              
    │  ├─home
    │  │      index.tsx
    │  │      
    │  ├─layout
    │  │      content.tsx
    │  │      footer.tsx
    │  │      header.tsx
    │  │      helper-sidebar.tsx
    │  │      index.tsx
    │  │      sidebar.tsx
    │  │      
    │  └─login
    │          index.tsx
    │          
    └─routes
            route-book.ts
            route-food-beverage-liquor.ts
            route.ts
            router.tsx

コーディングは以下のようになりました。

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\children-book\index.tsx

// src/page/book/children-book/index.tsx
import React from 'react'

const ChildrenBook: React.FC = () => {
    return (
        <div>
            <h1>ChildrenBook</h1>
        </div>
    );
}

export default ChildrenBook

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\commic\index.tsx

// src/page/book/commic/index.tsx
import React from 'react'

const Commic: React.FC = () => {
    return (
        <div>
            <h1>Commic</h1>
        </div>
    );
}

export default Commic

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\foreign-book\index.tsx

// src/page/book/foreign-book/index.tsx
import React from 'react'
import { Outlet } from 'react-router-dom';

const ForeignBook: React.FC = () => {
    return (
        <div>
            <h1>ForeignBook</h1>
        </div>
    );
}

export default ForeignBook

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\hard-cover\index.tsx

// src/page/book/hard-cover/index.tsx
import React from 'react'

const HardCover: React.FC = () => {
    return (
        <div>
            <h1>HardCover</h1>
        </div>
    );
}

export default HardCover

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\magazine\index.tsx

// src/page/book/magazine/index.tsx
import React from 'react'

const MagazineBook: React.FC = () => {
    return (
        <div>
            <h1>MagazineBook</h1>
        </div>
    );
}

export default MagazineBook

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\paper-back\index.tsx

// src/page/book/paper-back/index.tsx
import React from 'react'

const PaperBook: React.FC = () => {
    return (
        <div>
            <h1>PaperBook</h1>
        </div>
    );
}

export default PaperBook

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\picture-book\index.tsx

// src/page/book/picture-book/index.tsx
import React from 'react'

const PictureBook: React.FC = () => {
    return (
        <div>
            <h1>PictureBook</h1>
        </div>
    );
}

export default PictureBook

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\pocket-edition-book\index.tsx

// src/page/book/pocket-edition-book/index.tsx
import React from 'react'

const PocketEditionBook: React.FC = () => {
    return (
        <div>
            <h1>PocketEditionBook</h1>
        </div>
    );
}

export default PocketEditionBook

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\index.tsx

// src/page/book/index.tsx
import React from 'react'
import { Outlet, useLocation } from 'react-router-dom';

const Book: React.FC = () => {
    const location = useLocation();
    return (
        <div>
            {location.pathname === "/book" && <h1>Book</h1>}
            <Outlet />
        </div>
    );
}

export default Book

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\food-beverage\index.tsx

// src/page/food-beverage-liqour/food-beverage/index.tsx
import React from 'react';

const FoodBeverage:React.FC = () => {
    return (
        <div>
            <h1>FoodBeverage</h1>
        </div>
    );
}

export default FoodBeverage

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\beer\index.tsx

// src/page/food-beverage-liqour/liqour/beer/index.tsx
import React from 'react';

const Beer:React.FC = () => {
    return (
        <div>
            <h1>Beer</h1>
        </div>
    );
}

export default Beer    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\chuhai-and-cocktail\index.tsx

// src/page/food-beverage-liqour/liqour/chuhai-and-cocktail/index.tsx
import React from 'react';

const ChuhaiAndCocktail:React.FC = () => {
    return (
        <div>
            <h1>ChuhaiAndCocktail</h1>
        </div>
    );
}

export default ChuhaiAndCocktail 

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\low-malt-beer\index.tsx

// src/page/food-beverage-liqour/liqour/low-malt-beer/index.tsx
import React from 'react';

const LowMaltBeer:React.FC = () => {
    return (
        <div>
            <h1>LowMaltBeer</h1>
        </div>
    );
}

export default LowMaltBeer    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\non-alcoholic\index.tsx

// src/page/food-beverage-liqour/liqour/non-alcoholic/index.tsx
import React from 'react';

const NonAlcoholic:React.FC = () => {
    return (
        <div>
            <h1>NonAlcoholic</h1>
        </div>
    );
}

export default NonAlcoholic    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\sake\index.tsx

// src/page/food-beverage-liqour/liqour/sake/index.tsx
import React from 'react';

const Sake:React.FC = () => {
    return (
        <div>
            <h1>Sake</h1>
        </div>
    );
}

export default Sake  

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\shouchu\index.tsx

// src/page/food-beverage-liqour/liqour/shouchu/index.tsx
import React from 'react';

const Shouchu:React.FC = () => {
    return (
        <div>
            <h1>Shouchu</h1>
        </div>
    );
}

export default Shouchu  

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\western-liqours-and-liquers\index.tsx

// src/page/food-beverage-liqour/liqour/western-liqours-and-liqures/index.tsx
import React from 'react';

const WesternLiqoursAndLiquers:React.FC = () => {
    return (
        <div>
            <h1>WesternLiqoursAndLiquers</h1>
        </div>
    );
}

export default WesternLiqoursAndLiquers

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\wine\index.tsx

// src/page/food-beverage-liqour/liqour/wine/index.tsx
import React from 'react';

const Wine:React.FC = () => {
    return (
        <div>
            <h1>Wine</h1>
        </div>
    );
}

export default Wine

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\liqour\index.tsx

// src/page/food-beverage-liqour/liqour/index.tsx
import React from 'react';
import { Outlet, useLocation } from 'react-router-dom';

const Liqour:React.FC = () => {
    const location = useLocation();
    return (
        <div>
            {location.pathname === "/food-beverage-liquor/liquor" && <h1>Liqour</h1>}
            <Outlet />
        </div>
    );
}

export default Liqour

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\food-beverage-liqour\index.tsx

// src/page/food-beverage-liqour/index.tsx
import React from 'react';
import { Outlet, useLocation } from 'react-router-dom';

const FoodBeverageLiqour:React.FC = () => {
    const location = useLocation();
    return (
        <div>
            {location.pathname === "/food-beverage-liquor" && <h1>FoodBeverageLiqour</h1>}
            <Outlet />
        </div>
    );
}

export default FoodBeverageLiqour  

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\layout\content.tsx

// src/page/layout/content.tsx
import React from 'react'
import { Outlet, useLocation } from 'react-router-dom';
const Content: React.FC = () => {
    return (
        <div>
            <h1>Content</h1>
            <div>
            <Outlet />
            </div>
        </div>
    );
}

export default Content

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\layout\footer.tsx

// src/page/layout/footer.tsx
import React from 'react'

const Footer: React.FC = () => {
    return (
        <div>
            <h1>Footer</h1>
        </div>
    );
}

export default Footer    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\layout\header.tsx

import React from 'react'

const Header: React.FC = () => {
    return (
        <div>
            <h1>Header</h1>
        </div>
    );
}

export default Header

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\layout\helper-sidebar.tsx

// src/page/layout/helper-sidebar.tsx
import React from 'react'
import { Link } from "react-router-dom";
import routes from '../../routes/route'

interface RouteType {
  path: string;
  key?: string;
  element: React.ReactElement;
  children?: RouteType[];
}

// createLinks関数の引数と戻り値の型を指定する
const createLinks = (routes: RouteType[], parentPath = ""): React.ReactElement => {
  return (
    <>
      {routes &&
        routes.length > 0 &&
        routes.map((route, index) => {
          console.log(route);
          // 親要素のパスと現在のパスを連結する
          let fullPath = "";
          if (route.path !== "/") {
            fullPath = parentPath + "/" + route.path;
          }
          // route.key が 404 の場合はレンダリングをスキップする
          if (route.key === "404") {
            return null;
          }
          // route.key が home の場合は route.children のリンクだけを表示する
          if (route.key === "home") {
            return (
              <React.Fragment key={route.key}>
                {route.children && (
                  // If the route has children, create a nested list of links
                  // by calling the same function recursively
                  // 親要素のパスとしてfullPathを渡す
                  <ul>
                    {createLinks(route.children, fullPath)}
                  </ul>
                )}
              </React.Fragment>
            );
          }
          // li要素でリンクを囲む
          return (
            <li key={route.key}>
              <Link to={fullPath}>{route.key}</Link>
              {route.children && (
                // If the route has children, create a nested list of links
                // by calling the same function recursively
                // 親要素のパスとしてfullPathを渡す
                <ul>
                  {createLinks(route.children, fullPath)}
                </ul>
              )}
            </li>
          );
        })}
    </>

  );
};
export default createLinks;

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\layout\index.tsx

// src/page/layout/index.tsx
import React from 'react'
import Header from './header';
import Footer from './footer';
import Content from './content';
import Sidebar from './sidebar';


const Main: React.FC = () => {
    return (
        <div>
            <Header/>
            <h1>Main</h1>
            <div>
              <Sidebar/>
              <Content/>
            </div>
            <Footer/>
        </div>
    );
}

export default Main   

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\layout\sidebar.tsx

// src/page/layout/sidebar.tsx
import React from 'react'
import routes from '../../routes/route'
import CreateLinks from "../layout/helper-sidebar";

const Sidebar: React.FC = () => {
  console.log(routes)
    return (
        <div>
            <h1>Sidebar</h1>
              {CreateLinks(routes)}
        </div>
    );
}

export default Sidebar    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\404.tsx

// src/page/404.tsx
import React from 'react'

const Page404: React.FC = () => {
    return (
        <div>
            <h1>404</h1>
        </div>
    );
}

export default Page404  

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\routes\route-book.ts

// src/routes/route-book.ts
import React, { ReactElement } from "react";
import { BrowserRouter, Routes, Route, RouteObject } from "react-router-dom";

import Book from "../page/book";
import ForeignBook from "../page/book/foreign-book";
import Commic from "../page/book/commic";
import Magazine from "../page/book/magazine";
import HardCover from "../page/book/hard-cover";
import PocketEditionBook from "../page/book/pocket-edition-book";
import PaperBack from "../page/book/paper-back";
import PictureBook from "../page/book/picture-book";
import ChildrenBook from "../page/book/children-book";

// react-router-domのRouteObject[]型はkeyが無いので独自型を定義
export type RouteType = { 
  index?: boolean;
  key?: string; 
  path: string; 
  element: ReactElement; 
  children?: RouteType[];
};

const bookRoutes: RouteType[] = [
    {
      key: "book" 
      ,path: "book" // 本
      ,element: React.createElement(Book)
      ,children: [
        {
          index :true
          ,key: "foreignBook"
          ,path: "foreign-book" // 洋書
          ,element: React.createElement(ForeignBook)
        }
        ,{
          key: "commic"
          ,path: "commic" // 漫画
          ,element: React.createElement(Commic)
        }
        ,{
          key: "magazine"
          ,path: "magazine" // 雑誌
          ,element: React.createElement(Magazine)
        }
        ,{
          key: "hardCover"
          ,path: "hard-cover" // 単行本
          ,element: React.createElement(HardCover)
        }
        ,{
          key: "pocketEditionBook"
          ,path: "pocket-edition-book" // 文庫本
          ,element: React.createElement(PocketEditionBook)
        }
        ,{
          key: "paperBack"
          ,path: "paper-back" // 新書
          ,element: React.createElement(PaperBack)
        }
        ,{
          key: "pictureBook"
          ,path: "picture-book" // 絵本
          ,element: React.createElement(PictureBook)
        }
        ,{
          key: "childrenBook"
          ,path: "children-book" // 児童書
          ,element: React.createElement(ChildrenBook)
        }       
      ] 
    }
];

export default bookRoutes;   

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\routes\route-food-beverage-liquor.ts

// src/routes/route-food-beverage-liquor.ts
import React, { ReactElement } from "react";
//import { RouteObject } from "react-router-dom";

import FoodBeverageLiquor from "../page/food-beverage-liqour";
import FoodBeverage from "../page/food-beverage-liqour/food-beverage";
import Liquor from "../page/food-beverage-liqour/liqour";
import Beer from "../page/food-beverage-liqour/liqour/beer";
import LowMaltBeer from "../page/food-beverage-liqour/liqour/low-malt-beer";
import Wine from "../page/food-beverage-liqour/liqour/wine";
import Sake from "../page/food-beverage-liqour/liqour/sake";
import Shouchu from "../page/food-beverage-liqour/liqour/shouchu";
import WesternLiqoursAndLiquers from "../page/food-beverage-liqour/liqour/western-liqours-and-liquers";
import ChuhaiAndCocktail from "../page/food-beverage-liqour/liqour/chuhai-and-cocktail";
import NonAlcoholic from "../page/food-beverage-liqour/liqour/non-alcoholic";

// react-router-domのRouteObject[]型はkeyが無いので独自型を定義
export type RouteType = { 
  index?: boolean;
  key?: string; 
  path: string; 
  element: ReactElement; 
  children?: RouteType[];
};

const foodBeverageLiquorRoutes: RouteType[] = [
    {
      key: "foodBeverageLiquor"
      ,path: "food-beverage-liquor"// 食品・飲料・酒
      ,element: React.createElement(FoodBeverageLiquor)
      ,children : [
        {
          index: true
          ,key: "foodBeverage"
          ,path: "food-beverage" // 食品・飲料
          ,element: React.createElement(FoodBeverage)
        }
        ,{
          key: "Liquor"
          ,path: "liquor" // すべてのお酒
          ,element: React.createElement(Liquor)
          ,children: [
            {
              key: "Beer"
              ,path: "beer" // ビール
              ,element: React.createElement(Beer)
            }
            ,{
              key: "LowMaltBeer"
              ,path: "low-malt-beer" // 発泡酒
              ,element: React.createElement(LowMaltBeer)
            },
            {
              key: "Wine"
              ,path: "wine" // ワイン
              ,element: React.createElement(Wine)
            }
            ,{
              key: "Sake"
              ,path: "sake" // 日本酒
              ,element: React.createElement(Sake)
            }
            ,{
              key: "Shouchu"
              ,path: "shouchu" // 焼酎
              ,element: React.createElement(Shouchu)
            }
            ,{
              key: "WesternLiqoursAndLiquers"
              ,path: "western-liqours-and-liquers" // 洋酒・リキュール
              ,element: React.createElement(WesternLiqoursAndLiquers)
            }
            ,{
              key: "ChuhaiAndCocktail"
              ,path: "chuhai-and-cocktail" // チューハイ・カクテル
              ,element: React.createElement(ChuhaiAndCocktail)
            }
            ,{
              key: "NonAlcoholic"
              ,path: "non-alcoholic" // ノンアルコール飲料
              ,element: React.createElement(NonAlcoholic)
            }
          ]
        }
      ],
    },
];

export default foodBeverageLiquorRoutes;    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\routes\route.ts

// src/routes/route.ts
import React, { ReactElement } from "react";
//import { BrowserRouter, Routes, Route, RouteObject } from "react-router-dom";
import bookRoutes from "../routes/route-book";
import foodBeverageLiquorRoutes from "../routes/route-food-beverage-liquor";
import Page404 from "../page/404";
import Home from "../page/layout"

//const routes = [bookRoutes, foodBeverageLiquor];

// react-router-domのRouteObject[]型はkeyが無いので独自型を定義
export type RouteType = {
    index?: boolean;
    key?: string;
    path: string;
    element: ReactElement;
    children?: RouteType[];
};

// routesの要素をRouteTypeに合わせて定義
const routes: RouteType[] = [
  {
    key: "home"
    ,path: "/"
    ,element: React.createElement(Home)
    ,children: [
      bookRoutes[0]
      ,foodBeverageLiquorRoutes[0]
    ]
  }
  ,{
    key: "404"
    ,path: "*"
    ,element: React.createElement(Page404)      
  }
];
export default routes;

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\routes\router.tsx

// src/routes/router.tsx
// import { RouteObject } from "react-router-dom";
// import Home from "../page/home";
// import About from "../page/about";
// import Page404 from "../page/404";
//import { ReactElement } from 'react';
//import { useRoutes, Outlet } from "react-router-dom";
import { Outlet, RouteObject } from "react-router-dom";
import { useRoutes, BrowserRouter, Routes, Route } from 'react-router-dom';
import routes, { RouteType } from "../routes/route";

// export const Router: RouteObject[] = [
//     {
//       path: '/',
//       element: <Home />
//     },
//     {
//       path: '/about',
//       element: <About />
//     },
//     {
//       path: '*',
//       element: <Page404 />
//     }
//   ]

// useRoutesにRouterを渡してルーティングをレンダリング
const Router = () => { 
  const router = useRoutes(routes as RouteObject[])

  return router
  {/**
  return (
    

  <Routes>
    {
      routes.map((route: RouteType) => ( 
          <Route key={route.path} path={route.path} element={
            <>
            {route.element}
            <Outlet />

            </>
          } /> 
        )
      )
    } 
  </Routes> 

  ); 
*/}
};
export default Router;   

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\App.tsx

// src/App.tsx
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { useRoutes, BrowserRouter, Routes, Route, RouteObject, Outlet } from 'react-router-dom';
//import Router from './routes/router';
import React from 'react'
import Router from './routes/router';

const App: React.FC = () => {
  const [count, setCount]: [number, React.Dispatch<React.SetStateAction<number>>] = React.useState<number>(0)

  return (
    <div className="App">
      {/** 
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://reactjs.org" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
      */}
      {/* ルーター追加 */}
      {/* 
      <BrowserRouter>
        <Router/>
      </BrowserRouter>
      */}
      {/** 
      <>{router}</>
      */}
      <BrowserRouter>
      <Router />

      </BrowserRouter>

  
    </div>

  )
}

export default App

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\index.css

:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}
a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

h1 {
  font-size: 1.6em;
  line-height: 1.1;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}

で、開発用サーバーを起動して、

ブラウザからアクセスすると、画面が表示されました。

サイドバーの適当なリンクを押下すると、

Contentの中で、React Routerで定義したリンクのページに切り替わってることから、ページ遷移できました。

404ページは、ルーティングの定義でchildrenに含めていないので、<Outlet />で読み込めない感じなんですかね?

react-router-dom、何も分からん...

まぁ、ログインページとか404ページは共通ページにしないと思うから、これで良いとは思うけど。

ページ毎にサイドバーに表示するリンクの内容を変える、複数サイドバーとかには対応できてないけども。

それにしても、react-roter-domはドキュメントでTODOとか書いてくれてるのは親切なのかもしれんけど、使い方が全く分からな過ぎて、暗黙知にも程があるというね...

あと、Reactって利用してる人が多いって言われてる割に、実用的な情報が少ない気が...

フロントエンドの有識者の人に頑張って発信してもらいたい...

とりあえず、

qiita.com

⇧ JSXがつらい...

毎度モヤモヤ感が半端ない...

今回はこのへんで。