🌀

React: React Router v6 でルーティングする step2

2022/01/28に公開

2022年01月28日 Windows11での情報です。

前回に続いて、React Router v6 でのルーティングについて、見ていきます。

前回はこちら
React: React Router v6 でルーティングする step1

環境

  • vite: v2.7.2
  • node: v16.13.2
  • react: v17.0.2
  • typescript: v4.4.4
  • react-router-dom: v6.2.1

前回の記事でのコンポーネントの状態

SamplePage1は、シンプルなコンポーネントです。

components/SamplePage1
import React from "react";

export const SamplePage1:React.VFC =() => {
  return <h3>Sample Page 1</h3>
}

SamplePage2は、クエリーパラメータを使用したルーティング用のコンポーネントです。

components/SamplePage2
import React from "react";
import { useSearchParams } from "react-router-dom";

export const SamplePage2:React.VFC =() => {
  
  const [searchParams] = useSearchParams();

  const query1 = searchParams.get("query1");
  const query2 = searchParams.get("query2");


  return (
    <>
      <p>query1={query1}</p>
      <p>query2={query2}</p>
    </>
  );
}

SamplePage3は、propsを使用したルーティング用のコンポーネントです。

components/SamplePage3
import React from "react";

type Props = {
  Message: string 
};

export const SamplePage3:React.VFC<Props> = (props) => {
  return <p>{props.Message}</p>
}

NotFoundは、一致するURLがないルーティング用のコンポーネントです。

ccomponents/NotFound.tsx
import React from "react";

export const NotFound = () => {
  return (
    <>
      <h1>404</h1>
      <h3>お探しのページは見つかりませんでした。</h3>
    </>
  );
}

RouterConfigは、ルートを定義したコンポーネントです。

RouterConfig.tsx
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { SampleHome } from "./components/SampleHome";
import { SamplePage1 } from "./components/SamplePage1";
import { SamplePage2 } from "./components/SamplePage2";
import { SamplePage3 } from "./components/SamplePage3";
import { NotFound } from "./components/NotFound";

export const RouterConfig:React.VFC =() => {
  return (
    <>
     <BrowserRouter>
      <Routes>
        <Route index element={<SampleHome />} />
        <Route path="page1" element={<SamplePage1 />} />
        <Route path="page2" element={<SamplePage2 />} />
        <Route path="page3_hello" element={<SamplePage3 Message="Hello Router" />} />
        <Route path="page3_hi" element={<SamplePage3 Message="Hi Router"/>} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
    </>
  );
}

main.tsxでRouterConfigコンポーネントを読み込みます。
※ビルドツールviteでReactプロジェクトを作成するとmain.tsxが一番最初のコンポーネントになりますが、create-react-appでReactプロジェクトを作成すると、一番最初のコンポーネントはindex.tsxになります。(適当に読み替えてください)

main.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import { RouterConfig } from "./RouterConfig";

ReactDOM.render(
  <React.StrictMode>
   <RouterConfig />
  </React.StrictMode>,
  document.getElementById('root')
)

ボタンクリックでルーティングする

今までのサンプルでは、リンクでルーティングしてきました。
ボタンのクリックなどでルーティングを行う場合は、useNavigateフックが返すnavigate関数を使用します。

SampleHomeコンポーネントで「Sample Page1」ボタンをクリックすると、SamplePage1にルーティングするように追記します。

components/SampleHome.tsx
import React from "react";
import { Link } from "react-router-dom";
import { createSearchParams } from "react-router-dom";
import { RouterConfig } from "../RouterConfig";
import { useNavigate } from 'react-router-dom';

export const SampleHome:React.VFC =() => {

  const navigate = useNavigate();

  const params: string = createSearchParams({
    query1: "value3",
    query2: "value4"
  }).toString();


  return (
    <>
      <h1>Sample Home</h1>
      <nav>
        <ul>
          <li><Link to="page1">Sample Page1</Link></li>
          <li><Link to="page2">Sample Page2</Link></li>
          <li><Link to="page2?query1=value1&query2=value2">Sample Page2 With Query1</Link></li>
          <li><Link to={`page2?${params}`}>Sample Page2 With Query2</Link></li>
          <li><Link to="page3_hello">Sample Page3 Hello</Link></li>
          <li><Link to="page3_hi">Sample Page3 Hi</Link></li>
        </ul>

        <button onClick={() => navigate("page1")}>Sample Page1</button>
      </nav>  
    </>
  );
}

動作を確認してみます。

ネストされたページを表示する

次はネストされたページを表示する方法です。
「ネストされたページってなに?」ってことで、コードを書く前に、先に動作を確認します。

まずは前回作成したものから確認してみます。
page1リンクをクリックすると、page全体が変更されます。

今回作成するものです。
「Sample Page4」ページで「Show Child1」リンク、「Show Child2」リンクをクリックすると、それぞれのページの内容が、Sample Page4ページ内に表示されます。

今回新たに追加したSamplePage4コンポーネント、SamplePage4Child1、SamplePage4Child2です。
注目は<Outlet />です。
RouterConfigで定義している「ネストされたルート」のコンポーネントを<Outlet />部分にレンダリングします。
「Clear」ボタンでは、「""」を指定してルーティングしています。

components/SamplePage4.tsx
import React, { VFC } from "react";
import { Outlet, Link, useNavigate} from "react-router-dom";

export const SamplePage4:VFC = () => {
  const navigate = useNavigate();

  return (
    <>
      <h3>Sample Page 4</h3>
      <ul>
        <li><Link to="child1">Show Child1</Link></li>
        <li><Link to="child2">Show Child2</Link></li>
      </ul>
      <button onClick={()=> navigate("") }>clear</button>
      <Outlet  />
    </>
  );
}

export const SamplePage4Child1:VFC = () => {
  return <h3>Sample Page 4 Child1</h3>;
}
export const SamplePage4Child2:VFC = () => {
  return <h3>Sample Page 4 Child2</h3>;
}

ルートを定義したRouterConfigコンポーネントです。
ルート「path="page4"」の定義に、ネストしたルート「index」と「path="child1"」と「path="child2"」を指定しています。
「index」ではelementを指定していないので、SamplePage4コンポーネントの<Outlet />には何も表示されません。
SamplePage4の「Clear」ボタンのnavigate("")は、この「index」にルーティングされます。

RouterConfig.tsx
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { SampleHome } from "./components/SampleHome";
import { SamplePage1 } from "./components/SamplePage1";
import { SamplePage2 } from "./components/SamplePage2";
import { SamplePage3 } from "./components/SamplePage3";
import { SamplePage4, SamplePage4Child1, SamplePage4Child2 } from "./components/SamplePage4";
import { NotFound } from "./components/NotFound";

export const RouterConfig:React.VFC =() => {
  return (
    <>
     <BrowserRouter>
      <Routes>
        <Route index element={<SampleHome />} />
        <Route path="page1" element={<SamplePage1 />} />
        <Route path="page2" element={<SamplePage2 />} />
        <Route path="page3_hello" element={<SamplePage3 Message="Hello Router" />} />
        <Route path="page3_hi" element={<SamplePage3 Message="Hi Router"/>} />
        <Route path="page4" element={<SamplePage4 />} >
          <Route index />
          <Route path="child1" element={<SamplePage4Child1 />} /> 
          <Route path="child2" element={<SamplePage4Child2 />} /> 
        </Route>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
    </>
  );
}

ダイナミックルート

続いてはダイナミックルートです。

SamplePage4コンポーネントでは「Link to="123"」とルートを指定しています。
これはルートを定義したRouterConfigコンポーネントの「path=":cildid"」ルーティングされます。

新しくSamplePage4Child3コンポーネントを作成します。
useParamsフックを使用して、「cildid」パラメータを取得します。

components/SamplePage4.tsx
import React, { VFC } from "react";
import { Outlet, Link, useNavigate, useParams, NavigateFunction} from "react-router-dom";


export const SamplePage4:VFC = () => {
  const navigate:NavigateFunction = useNavigate();

  return (
    <>
      <h3>Sample Page 4</h3>
      <ul>
        <li><Link to="child1">Show Child1</Link></li>
        <li><Link to="child2">Show Child2</Link></li>
        <li><Link to="123">Show Child3</Link></li>
      </ul>
      <button onClick={()=> navigate("") }>clear</button>
      <Outlet  />
    </>
  );
}

export const SamplePage4Child1:VFC = () => {
  return <h3>Sample Page 4 Child1</h3>;
}
export const SamplePage4Child2:VFC = () => {
  return <h3>Sample Page 4 Child2</h3>;
}
export const SamplePage4Child3:VFC = () => {
  type Param = {
    cildid?: string
  }

  const params:Param = useParams<Param>();
  return (
    <>
      <h3>Sample Page 4 Child3</h3>
      <p>{`cildid=${params?.cildid}`}</p>
    </>
  );
}

RouterConfigコンポーネントでは「path=":cildid"」としてダイナミックルートを定義しています。

RouterConfig.tsx
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { SampleHome } from "./components/SampleHome";
import { SamplePage1 } from "./components/SamplePage1";
import { SamplePage2 } from "./components/SamplePage2";
import { SamplePage3 } from "./components/SamplePage3";
import { SamplePage4, SamplePage4Child1, SamplePage4Child2, SamplePage4Child3 } from "./components/SamplePage4";
import { NotFound } from "./components/NotFound";

export const RouterConfig:React.VFC =() => {
  return (
    <>
     <BrowserRouter>
      <Routes>
        <Route index  element={<SampleHome />} />
        <Route path="page1" element={<SamplePage1 />} />
        <Route path="page2" element={<SamplePage2 />} />
        <Route path="page3_hello" element={<SamplePage3 Message="Hello Router" />} />
        <Route path="page3_hi" element={<SamplePage3 Message="Hi Router"/>} />
        <Route path="page4" element={<SamplePage4 />} >
          <Route index  />
          <Route path="child1" element={<SamplePage4Child1 />} /> 
          <Route path="child2" element={<SamplePage4Child2 />} /> 
          <Route path=":cildid" element={<SamplePage4Child3 />} /> 
        </Route>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
    </>
  );
}

動作を確認してみます。

アクセスしているページのリンクを装飾する

最後にアクセスしているページのリンクを装飾してみます。
アクセスされているページのリンクを装飾するには、<Link>の代わりに<NavLink>を使用します。
<NavLink>は設定されたリンクが現在アクセス中であれば、isActiveプロパティにtrueが設定されます。
isActiveプロパティの値に応じて style か className のどちらかにスタイルやクラス文字列を設定することで、アクセスされているページのリンクを装飾します。

components/SamplePage4.tsx
import React, { VFC } from "react";
import { Outlet, NavLink, useNavigate, useParams, NavigateFunction} from "react-router-dom";


export const SamplePage4:VFC = () => {
  const active = {
    fontWeight: "bold",
    color: "#d57276"
  }

  const inactive = {
    fontWeight: "normal",
    color: "#65b2c6"
  }

  const navigate:NavigateFunction = useNavigate();

  const linkStyle = (isActive:boolean) => {
    return isActive ? active : inactive;
  }

  return (
    <>
      <h3>Sample Page 4</h3>
      <ul>
        <li><NavLink to="child1" style={({isActive}) => linkStyle(isActive)}>Show Child1</NavLink></li>
        <li><NavLink to="child2" style={({isActive}) => linkStyle(isActive)}>Show Child2</NavLink></li>
        <li><NavLink to="123" style={({isActive}) => linkStyle(isActive)}>Show Child3</NavLink></li>
      </ul>
      <button onClick={()=> navigate("") }>clear</button>
      <Outlet  />
    </>
  );
}

export const SamplePage4Child1:VFC = () => {
  return <h3>Sample Page 4 Child1</h3>;
}
export const SamplePage4Child2:VFC = () => {
  return <h3>Sample Page 4 Child2</h3>;
}
export const SamplePage4Child3:VFC = () => {
  type Param = {
    cildid?: string
  }

  const params:Param = useParams<Param>();
  return (
    <>
      <h3>Sample Page 4 Child3</h3>
      <p>{`cildid=${params?.cildid}`}</p>
    </>
  );
}

動作を確認してみます。

まとめ

2回にわたってReact Router v6を使用したルーティングについて、記事にまとめてみました。
簡単に使えてとても便利ですね。

Discussion