エンジニアを目指す日常ブログ

日々勉強したことのメモ。独学ですので間違っていたらコメント等で教えてください。

React×Typescriptでページ遷移時に値を渡す方法

はじめに

React×Typescriptでルーティングする際、前画面の値を次の画面に渡す方法を記載する。調べると「これが正解」というのが出てこなくて難しいが、理解した方法をメモ。

試した方法

  1. URLパラメータを使う(パスパラメータ)
  2. URLパラメータを使う(クエリパラメータ)
  3. <Link>コンポーネントにstateを渡す
  4. useHistory()を使う

前提

以下の記事で作成したアプリを利用して検証する。

tomiko0404.hatenablog.com

今回はreact-router-domのバージョン5を利用している。

URLパラメータで渡す方法

パスパラメータ

<Route>に渡すpathに以下のように:[パラメータ名]を記載することで、パスパラメータとして扱える。

<Route path="mapage/mypage-detail/:id" render={() => <MyPageDetail />} />

パラメータが任意の場合は?をつける。

<Route path="mapage/mypage-detail/:id?" render={() => <MyPageDetail />} />

期待する動作

  • localhost:8000にアクセスするとHomeページが表示される。(前回のまま)
  • localhost:8000/mypageにアクセスするとMyPageが表示される。(前回のまま)
  • localhost:8000/mypage/mapage-detailにアクセスするとMyPageDetailが表示される。(今回のポイント)
  • localhost:8000/mypage/mapage-detail/100というように、/100の部分にパスパラメータを入れてアクセスするとMyPageDetailが表示され、画面上にもパラメータの値が表示される。(今回のポイント)
  • リンクは常に表示される。

実際の動作
期待する動作

ソースコード

ルーティング側の設定

前回の記事で

<Route path={`${url}/mypage-detail`} render={() => <MyPageDetail />} />

としていた部分を

<Route path={`${url}/mypage-detail/:id?`} render={() => <MyPageDetail />} />

と書き換える。

Router.tsx全体

import { Switch, Route } from "react-router-dom";
import { Home } from "../pages/Home";
import { MyPage } from "../pages/MyPage";
import { MyPageDetail } from "../pages/MyPageDetail";

export const Router = () => {
    return (
        <Switch>
            <Route exact path="/" render={() => <Home />} />
            <Route
                path="/mypage"
                render={(props) => {
                   console.log(props)
                    const url = props.match.url;
                    return (
                        <Switch>
                            <Route exact path={url} render={() => <MyPage />} />
                            <Route path={`${url}/mypage-detail/:id?`} render={() => <MyPageDetail />} />
                        </Switch>
                    );
                }}
            />
        </Switch>
    );
};

今回はidを任意としているため、localhost:8000/mypage/mapage-detailにアクセスしてもが表示される。

idを必須とすればlocalhost:8000/mypage/mapage-detailは表示されない(ルーティングから外れる)。

パスパラメータの利用

useParamsというHooksが提供されており、パスパラメータを利用できる。 typescriptでは以下のように利用する。

MyPageDetail.tsx

import { useParams } from "react-router";

type Params = {
    id?: string;
};
export const MyPageDetail = () => {
    const { id } = useParams<Params>();

    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>idは{id}</h2>
        </>
    );
};

idはURLパラメータから渡されてきた値なのでstring型になる。

ちなみに以下の記事で、「typescriptを使っていても結局Router.tsxのpathを文字列で定義している以上、安全じゃないよね」ということが書かれている。確かに間違えてpath={`${url}/mypage-detail/:ib?`}としてしまっても気づけずバグを生みそう。

Template Literal Typesなどを使って解決する必要があるらしい。

2021年の密かなトレンド? “型安全ルーティング”の概観

クエリパラメータ

クエリパラメータの場合は、<Route>のpropsにあるlocationを利用する。

useLocation()を利用することで、locationオブジェクトを取得できる。

const { search } = useLocation();
const queryParams = new URLSearchParams(search);
const name = queryParams.get("name"); // nameはクエリパラメータ名の例

Routeのpropsのhistoryを利用する
Routeのpropsのhistoryを利用する

期待する動作

  • localhost:8000/mypage/mapage-detail/100?name=taro&age=20」というように、パスパラメータ、クエリパラメータを入れてアクセスすると、MyPageDetailが表示される。画面上にもパラメータの値が表示される。(今回のポイント)
  • それ以外はパスパラメータの節と同様。

ソースコード

ルーティング側の設定

パスパラメータのときと同じ。クエリパラメータはルーティングで設定する必要はない。

クエリパラメータの利用

利用する側のコンポーネントのソースは以下。

MyPageDetail.tsx

import { useLocation, useParams } from "react-router";

type Params = {
    id?: string;
};

export const MyPageDetail = () => {
    const { id } = useParams<Params>();
    const { search } = useLocation();
    const queryParams = new URLSearchParams(search);
    const name = queryParams.get("name");
    const age = queryParams.get("age");

    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>idは{id}</h2>
            <h2>nameは{name}</h2>
            <h2>ageは{age}</h2>
        </>
    );
};

<Link>からstateで渡す方法

期待する動作

  • localhost:8000/mypage/mapage-detail/100?name=taro&age=20」というように、パスパラメータ、クエリパラメータを入れてアクセスするのはURLパラメータの章と同様。
  • MyPageで定義した値(login、group)がMyPageDetailで利用でき、画面に表示できる。(今回のポイント)
  • それ以外はパスパラメータの節と同様。

locationオブジェクトのstate

useLocation()の中にあるstateを利用する。

先ほど、URLパラメータのみ設定していたとき、useLocationの中身は

hash: ""
key: "6sdwd9"
pathname: "/mypage/mapage-detail/20/"
search: "?name=taro&age=20"
state: undefined

となっている。

ここで使っていなかったstate部分を使う。

Routeのpropsの中身(location)
Routeのpropsの中身(location)

<Link>でstateを設定する

MyPage.tsxの中で

<Link to="/mypage/mapage-detail/20/?name=taro&age=20">
    link to MyPageDetail
</Link>

と定義している部分は、locationのオブジェクト名を使って以下のように書き換えられる。

<Link to={{ pathname: "/mypage/mapage-detail/20/", search: "?name=taro&age=20" }}>
    link to MyPageDetail
</Link>

結果(locationの中身)は先ほどと同じになる。

ここにstateを追加する。

<Link to={{ pathname: "/mypage/mapage-detail/20/", search: "?name=taro&age=20", state: myState }}>
    link to MyPageDetail
</Link>

このとき、locatoinオブジェクトのstateに値が設定されたことがわかる。

stateが追加された
stateが追加された

ソースコード

遷移元

ポイントは<Link>タグにstateを設定していることのみ。事前に定義したmyStateを設定。

Mypage.tsx

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

type MyState = {
    login: boolean;
    group: number;
};

export const MyPage = () => {
    // stateに設定する値(仮)
    const myState: MyState = {
        login: true,
        group: 1,
    };

    return (
        <div>
            <h1>Mypage</h1>
            <Link
                to={{
                    pathname: "/mypage/mapage-detail/20/",
                    search: "?name=taro&age=20",
                    state: myState,
                }}
            >
                link to MyPageDetail
            </Link>
        </div>
    );
};

遷移先(利用側)

useLocation()で取得できるlocationオブジェクトからstateオブジェクトを取り出す。

const { state } = useLocation<MyState>();

その際、stateオブジェクトの型をとして定義(今回はMyState)することが必要。

MyPageDetail.tsx

import { useLocation } from "react-router";

type MyState = {
    login: boolean;
    group: number;
};

export const MyPageDetail = () => {
    const { state } = useLocation<MyState>();
    const { group, login } = state;

    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>group:{group}</h2>
            <h2>login:{String(login)}</h2>
        </>
    );
};

useHistory()を使う方法

locationmatchと横並びで、<Route>コンポーネントのpropsに存在するhistoryオブジェクトを使う。 historyuseHistory()というHooks関数で利用することができる。

const onClick = () => history.push("/mypage/mypage-detail", myState);
return (
            <button onClick={onClick}>MyPageDetailへ</button>
);

historyの中身

historyの中身
historyの中身

history.push

history.push( path, state)は第一引数にpath、第二引数にstateを持つ関数。

公式ホームページに

新しいエントリを履歴スタックにプッシュします
React Router: Declarative Routing for React.js

とあるとおり、ページ遷移の履歴(history)に対してpathで指定した宛先を追加する機能。同時に、履歴の最新となる「pathで指定した宛先」に遷移できる。

history.goback

hisoty.goback()は、ブラウザの「戻る」と同じ動きをする関数。

期待する動作

<Link>からstateで渡す方法と同様だが、今回は<Link>ではなくボタンのonClick関数に遷移動作を定義する。

ソースコード

遷移元

ポイントは以下のようにonClick関数を定義する部分。

const history = useHistory();
const onClick = () => history.push("/mypage/mypage-detail", myState);
import { useHistory } from "react-router-dom";

type MyState = {
    login: boolean;
    group: number;
};

export const MyPage = () => {
    // stateに設定する値(仮)
    const myState: MyState = {
        login: true,
        group: 1,
    };

    const history = useHistory();
    const onClick = () => history.push("/mypage/mypage-detail", myState);
    console.log(history);

    return (
        <div>
            <h1>Mypage</h1>
            <div>
                <button onClick={onClick}>MyPageDetailへ</button>
            </div>
        </div>
    );
};

遷移先(利用側)

<Link>からstateで渡す方法のときと全く同じ。

const { state } = useLocation<MyState>();

のようにuseLocaton()を利用することでstateを取得できる。

historyとlocationの違い

以下の記事によれば、
historyは変わらないもの。locationはページ遷移のたびに変わるもの。」 blog.uhy.ooo

おわりに

以下4つの方法で、ページ遷移先に値を渡す方法を理解した。

  • URLパラメータを使う
    • パスパラメータ
    • クエリパラメータ
  • <Link>コンポーネントにstateを渡す
  • useHistory()を使う

途中でも書いたように、<Link>を使う場合でもhistory.push()を使う場合でも、パスやパラメータの設定が文字列となっており、タイプミスを引き起こしそうな点は気になるので引き続き調査する。

関連記事

react-router-domを使ったルーティング方法 tomiko0404.hatenablog.com

参考文献

www.udemy.com

zenn.dev