【React】React Routerを使った画面遷移

React

React のルーティングについて、こちらのUdemy教材を活用して学ばせてい頂いたことをまとめました。

Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版

https://www.udemy.com/course/react_stepup/learn/lecture/24823458#overview

React のルーティングについて の公式ドキュメントはこちら

React Router | Basics
Declarative routing for React apps at any scale

それでは行きましょう!

react-router-domをinstall

$ npm install react-router-dom

TypeScriptを使用する場合

$ npm install @types/react-router-dom

アプリのベースを作成

画面遷移の確認ができるようにまずはアプリのベースを作成します。

・react-router-domをimport

・Home, Page1, Page2コンポーネントを作成(3画面作ります。)

・App.jsで上記3つのコンポーネントを呼び出す

・BrowserRouterをimport
 これはこのコンポーネントで囲った配下でルーティングを有効にすると言った働きをするもの

react-router-domのLinkを使う場合は、全体をBrowserRouterで囲う必要がある。

・Linkをimport

・Linkタグでパスを指定

ここまでのコードがこちら

import { BrowserRouter, Link } from "react-router-dom";

import { Home } from "./Home";
import { Page1 } from "./Page1";
import { Page2 } from "./Page2";
import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/Page1">Page1</Link>
        <Link to="/Page2">Page2</Link>
        <Home />
        <Page1 />
        <Page2 />
      </div>
    </BrowserRouter>
  );
}

・Switchをimport
 Switchタグで、どのパスのときにどのパスに飛ばすかを指定していく。

・Routeをimport
 Routeタグで囲ってpathを指定し、そこに表示したいコンポーネントを入れる。

import { BrowserRouter, Link, Switch, Route } from "react-router-dom";

import { Home } from "./Home";
import { Page1 } from "./Page1";
import { Page2 } from "./Page2";
import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/Page1">Page1</Link>
        <Link to="/Page2">Page2</Link>
      </div>
      <Switch>
        <Route path="/Page1">
          <Page1 />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

こうすると以下のエラー

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports. Check the render method of `App`.

これは、React Routerのバージョン変更によるもの。

Routeの部分を以下のように書き換えることにより解決!

import { BrowserRouter, Link, Routes, Route } from "react-router-dom";

import { Home } from "./Home";
import { Page1 } from "./Page1";
import { Page2 } from "./Page2";
import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/Page1">Page1</Link>
        <Link to="/Page2">Page2</Link>
      </div>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/Page1" element={<Page1 />} />
        <Route path="/Page2" element={<Page2 />} />
      </Routes>
    </BrowserRouter>
  );
}

“react-router-dom”のバージョン5→6への変更に伴う変更点

Switchが使用できなくなったのと、Routesの追加、Routeの書き方。

変更部のみ抽出すると

      <Switch>
        <Route path="/Page1">
          <Page1 />
        </Route>
      </Switch>

     ↓

        <Route path="/Page1" element={<Page1 />} />

詳細はこちらのドキュメントに記載されています。

React Router | Upgrading from v5
Declarative routing for React apps at any scale

これで、画面遷移の実装をしていくためのベースができました!

これ以降はバージョンを”react-router-dom”: “5.3.0”として進めます。

ネストされたページへの遷移

ネストされたページヘの遷移をするためのルーティングの書き方についてです。

Page1配下にdetailAとdetailBのpathを作成し実装していきます。

・Page1の詳細画面AとPage1の詳細画面Bのコンポーネントをそれぞれ、Page1DetailAとPage1DetailBという名前で作成する。

・Page1にdetailAとdetailBのLinkを作成

・Linkをimport

import { Link } from "react-router-dom";

export const Page1 = () => {
  return (
    <div>
      <h1>Page1ページ</h1>
      <Link to="/page1/detailA">DetailA</Link>
      <Link to="/page1/detailB">DetailB</Link>
    </div>
  );
};

Routeは2通りの書き方がある。

アロー関数を使わない書き方
<Route path="/Page1">
  <Page1 />
</Route>

/Page1というpathでPage1のコンポーネントを呼び出す。

アロー関数を使った書き方
<Route path="/Page1" render={() => <Page1 />} />

renderのアロー関数の中身にPage1を返却する。

ここにネストルーティングを書いていく。

するとこうなる。

        <Route
          path="/Page1"
          render={() => (
            <Switch>
              <Route exact path="/page1">
                <Page1 />
              </Route>
              <Route exact path="/page1/detailA">
                <Page1DetailA />
              </Route>
              <Route exact path="/page1/detailB">
                <Page1DetailB />
              </Route>
            </Switch>
          )}
        />

アロー関数を使わない書き方だと、

        <Route path="/Page1">
          <Switch>
            <Route exact path="/page1">
              <Page1 />
            </Route>
            <Route exact path="/page1/detailA">
              <Page1DetailA />
            </Route>
            <Route exact path="/page1/detailB">
              <Page1DetailB />
            </Route>
          </Switch>
        </Route>

ネストの親を保証する

Page1配下はPage1であることの保証をする。

現状pathをpage1/と記述しているが、これを書かなくてもいいようにする。

Page1リンクをクリックした時のリンク先urlは、matchというobjectの中urlに記述されている。

そのため以下のように記述するとpathが取り出せる。

{ match: { url } }

        <Route
          path="/Page1"
          render={({ match: { url } }) => (
            <Switch>
              <Route exact path={url}>
                <Page1 />
              </Route>
              <Route exact path={`${url}/detailA`}>
                <Page1DetailA />
              </Route>
              <Route exact path={`${url}/detailB`}>
                <Page1DetailB />
              </Route>
            </Switch>
          )}
        />

こうすることで、タイポが理由でリンクを間違うといったことがなくなる。

ルート定義の分割

ルーティングも増えてくると、見にくいコードとなってしまいます。

そこでAppコンポーネントからルーティング部を別コンポーネントに分割、さらにその中のPage1を別コンポーネントに分割する方法について見ていきます。

・src > routerディレクトリを作成する。

ルーティングの設定は、src配下にrouterディレクトリを作成し、Router.sxファイルにするのが一般的です。

・Router.jsxを作成
 ここにRouteを移管し、Routerという名前でコンポーネント化

こうすることでApp.jsがスッキリする!

import { BrowserRouter, Link } from "react-router-dom";
import { Router } from "./router/Router.jsx";

import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/Page1">Page1</Link>
        <Link to="/Page2">Page2</Link>
      </div>
      <Router />
    </BrowserRouter>
  );
}

import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { Page1 } from "../Page1";
import { Page1DetailA } from "../Page1DetailA";
import { Page1DetailB } from "../Page1DetailB";
import { Page2 } from "../Page2";

export const Router = () => {
  return (
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>

      <Route
        path="/Page1"
        render={({ match: { url } }) => (
          <Switch>
            <Route exact path={url}>
              <Page1 />
            </Route>
            <Route exact path={`${url}/detailA`}>
              <Page1DetailA />
            </Route>
            <Route exact path={`${url}/detailB`}>
              <Page1DetailB />
            </Route>
          </Switch>
        )}
      />

      <Route path="/Page2">
        <Page2 />
      </Route>
    </Switch>
  );
};

ここから更にPage1を切り出す。

・router > Page1Routes.jsxを作成

・配列を作る

以下の部分を書き換える。

            <Route exact path={url}>
              <Page1 />
            </Route>
            <Route exact path={`${url}/detailA`}>
              <Page1DetailA />
            </Route>
            <Route exact path={`${url}/detailB`}>
              <Page1DetailB />
            </Route>

できたのがこちら

import { Page1 } from "../Page1";
import { Page1DetailA } from "../Page1DetailA";
import { Page1DetailB } from "../Page1DetailB";

export const page1Routes = [
  {
    path: "/",
    exact: true,
    children: <Page1 />
  },
  {
    path: "/DetailA",
    exact: false,
    children: <Page1DetailA />
  },
  {
    path: "/DetailB",
    exact: false,
    children: <Page1DetailB />
  }
];

pathとexactとchildrenであるコンポーネントを切り分けて配列の形として、定数page1Routesにまとめる。

その定数をmapで展開する。

import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { page1Routes } from "./Page1Routes";
import { Page2 } from "../Page2";

export const Router = () => {
  return (
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>

      <Route
        path="/Page1"
        render={({ match: { url } }) => (
          <Switch>
            {page1Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />

      <Route path="/Page2">
        <Page2 />
      </Route>
    </Switch>
  );
};

URLパラメータを扱う

pathのidを取得してそのidのページを表示する方法について見ていきます。

・表示用のUrlParameter.jsxコンポーネントを作成する。

・Routeのコンポーネントを作成

URLパラメータの場合はpathの書き方がちょっと違った書き方をする。

/:パラメータ名

今回は以下の形で使用する
path: "/:id"

useParamsというHooksを使う。

このuseParamsは、関数が実行されるとidを受け取ることができるもので、これで、渡されたidのpathを表示できるようになる。

まとめると

Linkのページ

import { Link } from "react-router-dom";

export const Page2 = () => {
  return (
    <div>
      <h1>Page2ページ</h1>
      <Link to="/page2/100">URL Parameter</Link>
    </div>
  );
};

Route設定のファイル

import { Route, Switch } from "react-router-dom";
import { page2Routes } from "./Page2Routes";

export const Router = () => {
  return (
    <Switch>
      <Route
        path="/Page2"
        render={({ match: { url } }) => (
          <Switch>
            {page2Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />
    </Switch>
  );
};

上記のRouteのmapの展開

import { Page2 } from "../Page2";
import { UrlParameter } from "../UrlParameter";

export const page2Routes = [
  {
    path: "/",
    exact: true,
    children: <Page2 />
  },
  {
    path: "/:id",
    exact: false,
    children: <UrlParameter />
  }
];

pathのidを取得しているファイル

import { useParams } from "react-router-dom";

export const UrlParameter = () => {
  const { id } = useParams();
  return (
    <div>
      <h1>UrlParameterページです。</h1>
      {id}
    </div>
  );
};

画面遷移時の情報の受け渡し

画面遷移時に遷移元の情報を遷移先に渡す方法について見ていきます!

Page1の情報をPage1/DetailAに持っていく場合

・仮で100件の配列を作る。

const arr = [...Array(100).keys()];

これを画面遷移時に持っていきたい。

Linkを書き換える。

<Link to="/page1/detailA">DetailA</Link>

     ↓

<Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>

stateにarrを設定していることで、画面遷移時に、arrを持っていくことができる。

受け取り側ではuseLocationを使う。

DetailAに渡っているかconsoleで確認する。

import { useLocation } from "react-router-dom";

export const Page1DetailA = () => {
  const { state } = useLocation();
  console.log(state);
  return (
    <div>
      <h1>Page1DetailAページ</h1>
    </div>
  );
};

consoleを確認すると、最初に作った100件の配列が渡っていることが確認できる。

Linkを使わないページ遷移

これまで、Linkを使用した遷移方法を見てきましたが、Linkを使用しないで、JavaScriptを使って遷移させる方法について見ていきます!

useHistoryというHookを使う。

・ボタンにonClickイベントを設定

・その関数の中でpushという関数を使い、その中にリンクpathを記述

import { useHistory } from "react-router-dom";

export const Page1 = () => {

  const history = useHistory();

  const onClickDetailA = () => history.push("/page1/detailA");

  return (
    <div>
      <h1>Page1ページ</h1>
      <button onClick={onClickDetailA}>detailAへのリンク</button>
    </div>
  );
};

多くの画面遷移はこれを使う!!

このuseHistoryもまた、v6へのバージョンアップに伴い、変更がされており、

useNavigateとなり、pushは不要に変更となっている。

詳細はこちら↓

React Router | Upgrading from v5
Declarative routing for React apps at any scale

まとめ

以上、画面遷移についてでした。

いろんなリンクの書き方があったり、リンクもコンポーネントで分けたり、更に、mapを活用して配列展開したりと、なかなか奥がふかいですね!

コメント

タイトルとURLをコピーしました