Reactでページ遷移する方法を調べました【React/React Router/react-router-dom/v5】

JavaScript

Reactでページ遷移する方法を調べました。忘れそうなのでメモしておきます。
TypeScriptで記述していますが、設定の方法は普通のJavaScriptとあまり変わらないと思います。

react-router-domとは

ReactでSingle Page Application(SPA)を作る際、react-router-domというパッケージを使うと、コンポーネント間の画面遷移が可能になります。
仕組みとしては、あるURLにアクセスした際、そのURLに紐づいたコンポーネントを表示させることができます
こうすることで、本来単一ページであるSPAを、複数のページが存在しているかのように見せることができます。

ちなみに、こういった設定のことをルーティングと呼ぶそうです。

react-router-domをインストールする

まず、reactをインストールしたディレクトリに移動してreact-router-domをインストールします。次のコマンドを実行します。

$ npm i react-router-dom@5.2.0

今回使用するバージョンは5.2.0です。最新バージョンは6ですが、それだと資料が少なくて初心者には敷居が高かったです、、。

今回、練習もかねてTypeScriptでコードを書くので、react-router-domに対応する型情報もインストールしておきます。@typesで始まるパッケージ名は型情報が書かれたものになります。

$ npm i @types/react-router-dom

package.jsonは次のようになりました。他にもTypeScriptに関する型情報をインストールしていますが、ここではこんなパッケージもインストールするんだな。くらいの認識です、、。

package.json

ルーティングの基本設定をする

早速ルーティングを設定したいと思います!まず、react-router-domから必要な機能をインポートします。

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

基本の使い方は次の通りです。

<BrowserRouter>
  <Link または NavLink to="<リンクのパス>">テキスト</Link または NavLink>
  <Switch>
    <Route path="<コンポーネントと紐づけるパス>">
      <表示するコンポーネント>
    </Route>
  </Switch>
</BrowserRouter>

ルーティングするコードを<BrowserRouter>で囲み、画面を切り替える箇所を<Switch>で囲みます。
コンポーネントに紐づけるパスは<Route>で指定します。

また、画面遷移のパス指定にはaタグではなく<Link>または<NavLink>を使います。

言葉だと分かりにくいので、画面遷移する簡単なサンプルを作成しました。
「About」のリンクをクリックすると、URLに合わせてページが遷移しているのを確認できます。

コード全体はこんなカンジです。
単一ページでありながら、URLに合わせて<TopPage />と<About />コンポーネントが切り替わっていることが分かります。

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

const TopPage: VFC = () => {
  return <h1>TopPage</h1>;
};
const About: VFC = () => {
  return <h1>About</h1>;
};

export default function App() {
  return (
    <div className="app">
      <BrowserRouter>
        <ul>
          <li>
            <NavLink to="/">Top</NavLink>
          </li>
          <li>
            <NavLink to="/about">About</NavLink>
          </li>
        </ul>
        <Switch>
          <Route exact path="/">
            <TopPage />
          </Route>
          <Route path="/about">
            <About />
          </Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}

指定するパスは部分一致になる

パスを指定する際の注意事項としては、パスが部分一致になることです。
今回の場合、<TopPage />コンポーネントのパスは「/」になりますが、これだと部分一致のため「/about」も含まれてしまいます。

Routeでパスの完全一致をするには、以下のようにexactをつけます。

<Route exact path="/"> // pathの完全一致
  <TopPage />
</Route>
<Route path="/about">
  <About />
</Route>

アクティブになっているリンクをカスタマイズする【NavLink】

<Link>で画面遷移するパスを指定しますが、<NavLink>もあります。機能はほとんど変わらないらしいですが、NavLinkはアクティブな状態にクラス名を付与できます。使い方は、activeClassNameでクラス名を指定するだけです。

<NavLink exact activeClassName="active" to="/">

実際のHTMLは次のように出力されます。先ほど指定したクラス名「active」が設定されています。

<a aria-current="page" class="active" href="/">Top</a>

パスを入れ子(ネスト)する

パスを「/example/item1/」のように入れ子にしたい時があると思います。その際は単純にRouteをSwitchで入れ子にすれば実装できました。

<Switch>
  <Route exact path="/">
    <TopPage />
  </Route>
  <Route path="/item">
    <Switch>
      <Route exact path="/item">
      <ItemPage />
      </Route>
      <Route path="/item/product01">
        <Product01 />
      </Route>
    </Switch>
  </Route>
  <Route path="/about">
    <About />
  </Route>
  <Route path="*">
    <Page404 />
  </Route>
</Switch>

入れ子にしたのはitemのパスです。上記の場合、「/item」と「/item/product01/」のパスが生成できます。

ちょっとだけ省略したいので、Routeのrenderという機能を使います。

<Route path="/item" render={(res) => console.log(res)} />
// resの中身
{history: Object, location: Object, match: Object, staticContext: undefined}

このように記述すると、historyやlocationなど様々なobjectが取得できます。matchの中身を見てみます。

match: Object
▶︎path: "/item"
▶︎url: "/item"
▶︎isExact: true
▶︎params: Object

path、urlのプロパティにパスの値が格納されています。この値を使って書き直したコードがこちらです。itemの部分だけを抜き出しました。

<Route
  path="/item"
  render={({ match: { path } }) => (
    <Switch>
      <Route exact path={`${path}`}>
        <ItemPage />
      </Route>
      <Route path={`${path}/product01`}>
        <Product01 />
      </Route>
      <Route path={`${path}/product02`}>
        <Product02 />
      </Route>
    </Switch>
  )}
/>

これだと親パスを間違えることもないし、renderの中に記述をまとめることができます。少しだけスッキリしたコードになりました。

1つ前に戻るリンクを作成する【useHistory】

1つ前のページに戻りたい時、直接<Link>でパスを指定しても良いのですが、動的にパスが変更になる場合もあると思います。その場合useHistory()を使います。履歴に関する機能が色々と使えます。

使用するには、useHistory をimportします。

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

useHistoryは関数なので、一度変数に格納すると使いやすいです。

const history = useHistory();

useHistory()の中に、goBack()という1つ前に戻る関数があるのでこれを使います。
実際のコードはこちらになります。

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

export const Product02: VFC = () => {
  const history = useHistory();
  return (
    <div className="item_page_product">
      <p className="ttl">./pages/ItemProduct02.tsx</p>
      <h1>Product02</h1>
      <button onClick={() => history.goBack()}>戻る</button>
    </div>
  );
};

goBack()以外でよく使いそうなのはpush()です。こちらの関数を使うとパスを追加=URLが変更になるのでページ遷移ができます。<Link>が使えない時に使用します。

404ページを作成する

存在しないURLにアクセスすると、真っ白な画面が表示されてしまいます。
これだとアプリとして不便なので、Not Found(404)のページを表示させます。

Not Found(404)コンポーネントを準備して、<Route>で読み込む訳ですが、その際、pathを「*」に設定します。「*」はなんでも良い文字列を表します。このRouteを最後に追加することで、存在しないURLの場合はNot Found(404)コンポーネントが表示されます。

<Switch>
  <Route exact path="/">
    <TopPage />
  </Route>
  <Route path="/about">
    <About />
  </Route>
  <Route path="*">
    <Page404 />
  </Route>
</Switch>

実際の動きを確認する

今までのコードをまとめたものがこちらです。実際の動きが確認できます。
先程のコードと違い、コンポーネントは外部ファイルに分割して、読み込むことで画面を表示させています。

useParamsを使って遷移先に値を渡す

URLにセットしたパラメーターを、遷移先のコンポーネントで使うにはuseParams()を使います。

ここでのパラメーターは「https://example.com/item/1234」の「1234」の部分です。この値を遷移先のコンポーネントで使用します。

イメージとしてはこんなカンジです。

遷移先のコンポーネントでは、受け取ったURLパラメーターを画面に表示しています。
リンクで遷移しなくても、URLのパラメーターを変更したら画面が変更されるのがポイントです。

useParamsの使い方

使用するには、遷移先のコンポーネントにuseParams をimportします。

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

リンクで遷移させる場合<Link>でパラメータ付きのリンクを指定します。ここでのパラメーターは「1234」です。

<Link to="/user/1234">ユーザー1234のページに移動</Link>

<Route path=”<遷移先のパス>”>で指定するパスに、受け渡すパラメーターのキーとなる名前を「:」で指定します。名前はなんでもOKです。
以下はuseridという名前で遷移先のコンポーネントにパラメーターを渡しています。

<Route path="/user/:userid">
  <UserPage />
</Route>

遷移先のコンポーネントでuseParams()の中身を確認すると「userid」プロパティの値としてURLパラメータ「1234」が文字列で格納されていることが分かります。この値を使って色々なことができる、、はず。

const params = useParams();
// paramsの中身
{userid: "1234"}

こちらがコード全体になります。useParams()に関係がある箇所を色付けしました。

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

const UserPage: VFC = () => {
  const params: {userid: string} = useParams();
  const userid = params.userid;
  console.log(params)
  return (
    <div className="user">
      <h1>ユーザーページ</h1>
      <p>idは{userid}です。</p> // URLパラメーターを表示
    </div>
  );
};

export default function App() {
  return (
    <div className="container">
      <BrowserRouter>
        <Link to="/user/1234">ユーザー1234のページに移動</Link>
        <Switch>
          <Route path="/user/:userid">
            <UserPage />
          </Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}

サンプルを作ってみる

useParams()を使って簡単な占いアプリを作りました。ボタンをクリックするごとに異なる占い結果が表示されます。ハマりそうなポイントだけメモしておきます。

占いの結果は、配列に格納したオブジェクトの値を参照します。

const fortuneItem = [
  { id: 1, content: "大吉" },
  { id: 2, content: "中吉" },
  { id: 3, content: "小吉" },
  { id: 4, content: "凶" }
];

占いボタンをクリックしたら、URLパラーメーターにランダムな数字が割り当てられるようにします。
ボタンをクリックするごとに「/result/1〜4」のパスが生成されます。

FortunePage.tsx

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

export const FortunePage: VFC = () => {
  const history = useHistory();
  //1〜4のランダムな数字
  const randomNum = (): number => {
    return Math.floor(Math.random() * 4) + 1;
  };
  return (
    <div className="search">
      <h1>今日の運勢を占います</h1>
      <button
        className="fortune_btn"
         //クリックするごとにパスを生成
        onClick={() => history.push(`/result/${randomNum()}`)}
      >
        今日の運勢を見る
      </button>
    </div>
  );
};

idの値に合致するオブジェクトを抽出する

URLパラメータ1〜4の値で、表示する占いの結果を変化させます。
以下は、idが1のオブジェクト { id: 1, content: “大吉” }を変数targetObjに格納しています。

Result.tsx

const fortuneItem = [
  { id: 1, content: "大吉" },
  { id: 2, content: "中吉" },
  { id: 3, content: "小吉" },
  { id: 4, content: "凶" }
];
const targetObj = fortuneItem.find((f) => f.id === 1);

パラメーターのないURLのページを表示させる

URLパラメーターがない場合のルーティングも設定しておきます。今回、紐づけるコンポーネントは同じです。

App.tsx

<BrowserRouter>
  <Switch>
     // URLパラメーターがない場合
    <Route exact path="/">
      <FortunePage />
    </Route>
    // URLパラメーターがある場合
    <Route path="/result/:foutuneresult">
      <Result />
    </Route>
    <Route path="*">
      <Page404 />
    </Route>
  </Switch>
</BrowserRouter>

遷移先のコンポーネントで、URLパラメータの存在の有無で表示を切り分けます。
今回、targetObj にURLパラメーターによって取得したオブジェクトが含まれています。
この値がfalseならばパラメーターがない状態です。これで表示を切り替えます。

Result.tsx

<p className="result_txt">
  {targetObj ? (
    targetObj.content
  ) : (
    <span className="result_error">エラーが発生しました。</span>
  )}
</p>

useLocationを使ってURLパラメーターの値を取得する

URLのパラメーターを、コンポーネントで使用するにはuseLocation()を使います。
以下は、useLocation()を使ってURLパラメーターの値を画面に表示しています。

ここでのURLパラメーターはURLの?以降の文字列です。これとは別にページ内リンクで使う「#」以降の文字列も取得できます。

useParamsの使い方

使用するには、遷移先のコンポーネントにuseLocation をimportします。

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

useLocation()は関数なので、一度変数に格納すると使いやすいです。

const location = useLocation();

次のURLの場合、

https://exapmle.com/result/?keyword=abcd#gotolink

useLocation()の中身を確認すると、パスやパラメーターなど、location(URL)に関する情報が表示されます。

const location = useLocation();
// locationの中身
▶︎{pathname: "/result/", search: "?keyword=def", hash: "#hash", state: undefined}

URLパラメータは「search」プロパティ、「#」は「hash」プロパティで取得できていることがわかります。

サンプルを作ってみる

useLocation()の動きが確認できるサンプルを作りました。
フォームに入力した文字列が検索結果のページに表示されます。
また、決まった値を遷移先に渡すリンクも作成しました。
ポイントだけメモしておきます。

ルーティングの設定だけを抜き出しました。
トップと検索結果を表示するページをルーディングで切り替えています。

App.tsx

<div className="container">
  <BrowserRouter>
    <Switch>
      <Route exact path="/">
        <TopPage />
      </Route>
      <Route path="/result/">
        <ResultPage />
      </Route>
      <Route path="*">
        <Page404 />
      </Route>
    </Switch>
  </BrowserRouter>
</div>

検索結果のページではuseHistory()を使ってURLからパラメータの値を取得します。
関連するコードに色付けしました。

ResultPage.tsx

import { VFC } from "react";
import { useLocation } from "react-router-dom";
import { useHistory } from "react-router-dom";
import { SearchInput } from "../componenets/SearchInput";

export const ResultPage: VFC = () => {
  const location = useLocation();
  // クエリパラメーターを取得
  const search = location.search;
  // useHistoryを取得
  const history = useHistory();
  // 値だけ取り出す
  const query = new URLSearchParams(search);

  return (
    <>
      <SearchInput />
      <div className="result  content">
        <h1>検索結果</h1>
        <p>あなたが検索したのは</p>
        <p>「{query.get("keyword")}」です。</p>
        <button onClick={() => history.push("/")}>トップへ戻る</button>
      </div>
    </>
  );
};

まず、useLocation()を使っている箇所です。

// useHistory()を変数に格納  
const location = useLocation();
  // URLパラメーターを取得
  const search = location.search;

特に難しいところはないのですが、このままだと「?keyword=abc」みたいな値で取得されます。
このままだと使いづらいし、正規表現で置換するのも面倒です。
その場合、次のようにnew URLSearchParams()を使うと、「abc」の値だけ抜き出すことができます。

const location = useLocation();
const search = location.search;

// 値だけ取り出す
  const query = new URLSearchParams(search);

stateを使って遷移先に値を渡す

先程確認したlocationの中身に「state」というプロパティがありました。

const location = useLocation();
// locationの中身
▶︎{pathname: "/result/", search: "?keyword=def", hash: "#hash", state: undefined}

useParams()だとURLパラメーターの値しか引き渡すことができなかったのですが、
state」を使えば、画面を遷移する際に好きな値を引き渡すことができます。

使い方は、まずリンクを次のように設定します。

<Link to={{ pathname: "<path>" ,state: "<引き渡す値>"}}>テキスト</Link>

//例
<Link to={{ pathname: "/result/", state: "あいうえお" }}>値(あいうえお)を引き渡して遷移</Link>

遷移先のコンポーネントでuseLocation()を使います。
「state」プロパティを参照すると、Linkで設定した値を取得することができます。

以下は「state」に関する箇所を抜きだしたコードになります。

ResultPage.tsx

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

export const ResultPage: VFC = () => {
  const location = useLocation();
  return (
     <p>stateで引き渡された値は「{location.state}」です。</p>
  );
};

これで好きな値を遷移先に渡すことができました!

まとめ

react-router-domには、他にもリダイレクトなど様々な機能があるそうです。
基本的なことはまとめたので、あとは使いながら覚えていきたいと思います!


コメント