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には、他にもリダイレクトなど様々な機能があるそうです。
基本的なことはまとめたので、あとは使いながら覚えていきたいと思います!
コメント