この記事では、React コミュニティにおけるルーティングライブラリのデファクトスタンダード React Router の基本的な機能を紹介します。
以前の記事『Vue.jsエンジニアのためのReact入門』と同様に、主に Vue Router をひととおり学習した方向けに、React Router ならどうやるのか?という視点でまとめます。
とはいえ前記事よりはゆるい比較なので、Vue Router 知らない方でも React Router の入門として読めると思います。
ルーティングライブラリとは
Vue Router 知っている皆さんには説明不要かもしれませんが、おさらいです。
Vue Router も React Router も、「ルーティングライブラリ」と呼ばれます。
ルーティングライブラリは、SPA(Single Page Application)には欠かせない存在で、紙芝居のように URL に応じて表示するコンポーネントを切り替えることで、画面遷移をエミュレートします。
そのためには、最低限以下の機能を持っている必要があります。
- URL パターンとコンポーネントの紐づけ
- URL 中に含まれる動的パラメータの定義と取得(たとえば
/users/1234
の1234
) - プログラムによるナビゲーション
Vue Router と React Router はいずれもこのような機能、およびそれを実現するための独自コンポーネントを提供しています。
その意味ではどちらも同じようなライブラリですが、決定的に異なるのは、Vue Router が豊富な「ナビゲーションガード」を持っている点でしょう。ナビゲーションガードとは、ナビゲーション発生から完了までの色々なタイミングに処理をフックできる機能です(次のコンポーネントをレンダリングする直前に何かするなど)。
React Router はナビゲーションガードの機能を明示的には持っていません。ただ、似たようなことを実現する方法は紹介します。
インストール
Web アプリで使用する場合は、react-router-dom
ライブラリをインストールします。
$ npm install react-router-dom
モバイルアプリ用の react-router-native
やそれぞれのコアである react-router
も存在しますので、注意しましょう。
ルートマッチング
URL パターンとコンポーネントの紐づけは以下のように実装します。
import { BrowserRouter, Switch, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact children={<Index />} />
<Route path="/about" children={<About />} />
</Switch>
</BrowserRouter>
);
}
react-router-dom
から3つのコンポーネント BrowserRouter
, Switch
, Route
をインポートしています。この3つはセットで覚えてしまうとよいでしょう。
BrowserRouter
BrowserRouter
の内部でルーター機能が使えるようになります。
BrowserRouter
は HTML5 History API を用いるタイプのルーターで、他には HashRouter
コンポーネントもあります。ただ、あえて HashRouter
を選ぶケースはほとんどないでしょう。
basename
プロパティで URL パターンをグルーピングできます。以下の Route
は /settings/profile
にマッチします。
<BrowserRouter basename="/settings">
<Switch>
<Route path="/profile" children={<MyComponent />} />
</Switch>
</BrowserRouter>
Switch
Switch
コンポーネントは、排他的なルートの描画にために使います。
そもそも Route
は、マッチさえすれば登録されたコンポーネントを描画します。以下のように書いた場合(2つ目はパスパラメータ、3つ目はワイルドカード、どちらも詳しくは後述します)、/cat
はすべてにマッチするので、3つのコンポーネントが描画されます。
<Route path="/cat" children={<Cat />} />
<Route path="/:userId" children={<User />} />
<Route path="*" children={<NotFound />} />
Switch
で囲むと、上から検索していって、マッチした Route
のみを描画します。
<Switch>
<Route path="/cat" children={<Cat />} />
<Route path="/:userId" children={<User />} />
<Route path="*" children={<NotFound />} />
</Switch>
たいていのケースでは Switch
を利用することになるでしょう。
Route
Route
は、URL パターンごとに描画されるコンポーネントを定義します。
path
に URL パターン、children
に対応するコンポーネントを指定します。つまり、URL が path
の値にマッチしたとき、children
に指定されたコンポーネントを描画します。
以下、プロパティについて簡単に紹介しますが、これ以外にもあるので詳細はドキュメントを参照してください。
path
URL パターンの書き方は、Vue Router と同じです。どちらも path-to-regexp というライブラリを使用しているためです。
path
には配列で複数のパターンを登録できます。
<Route path={['/users', '/profiles']} children={<UserList />} />
children
React 的には、以下の二つの書き方は同じ意味です。
<Route path="/abc" children={<MyComponent />} />
<Route path="/abc">
<MyComponent />
</Route>
子要素は children
というプロパティ名で渡されるからです。
exact
exact
プロパティは、URL パターンを完全一致でマッチング判定させたい場合に付与します。
たとえば、exact
を付けずに以下のように書いた場合、/about
は一個目の /
にマッチしてしまい、<About />
ではなく <Index />
が表示されます。デフォルトの判定は前方一致だからです。
<Switch>
<Route path="/" children={<Index />} />
<Route path="/about" children={<About />} />
</Switch>
exact
を付ければ、その path
に完全一致しない限りはマッチしません。以下のように書けば、/about
は /
にマッチしなくなります。
<Switch>
<Route path="/" exact children={<Index />} />
<Route path="/about" children={<About />} />
</Switch>
Link
マッチングの定義をしたパスへのリンクには Link
コンポーネントを利用します。
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">index</Link>
<Link to="/about">about</Link>
</nav>
<Switch>
<Route path="/" exact children={<Index />} />
<Route path="/about" children={<About />} />
</Switch>
</BrowserRouter>
);
}
Link
は最終的には <a>
に描画されるので、title
や className
などアンカー要素に付与できる属性は Link
にも付与できます。
<Link to="/" title="..." className="...">index</Link>
to
プロパティの指定方法は、文字列、オブジェクト、関数の3種類あります。それぞれ簡単に紹介します。それ以外のプロパティも含めて、詳細はマニュアルを参照してください。
String
文字列での指定はシンプルですね。パスをそのまま指定します。
<Link to="/cat">猫</Link>
Object
オブジェクト形式では、クエリパラメータや追加データ(state
)などを指定できます。
<Link to={{
pathname: '/cat', // パス文字列
search: '?breed=persian', // クエリパラメータ
hash: '#the-hash', // URLハッシュ
state: { fromDogPage: true } // 任意のデータ
}}>ペルシャ猫</Link>
ちなみに state
データは遷移先のコンポーネントで、location
オブジェクトから参照できます。location
オブジェクトは、useLocation
フックを使って取得します。
import { useLocation } from 'react-router-dom';
function Cat() {
const location = useLocation();
if (location.state) {
console.log(location.state.fromDogPage);
}
// 以下略...
}
Function
関数で渡すパターンは少し見た目がややこしいですが、「現在の location
」を引数に取って、上述の文字列またはオブジェクトを返却します。
<Link to={location => `${location.pathname}?breed=persian`}>ペルシャ猫</Link>
現在の location
を加工したリンクを生成する場合に使用するのでしょう。
設定ファイル化
Vue Router は、ルーティングの設定を配列にまとめて定義します。React Router 自体にはそのような書き方は存在しませんが、単純に JavaScript コードとして、たとえば以下のようにまとめることはできます。
const routes = [
{
path: '/',
exact: true,
children: <Index />
},
{
path: '/about',
children: <About />
}
];
export default routes;
import routes from './routes.js';
function App() {
return (
<BrowserRouter>
<Switch>
{routes.map((config, i) => (
<Route key={i} {...config} />
))}
</Switch>
</BrowserRouter>
);
}
他にもユースケースに応じて色々な書き方ができるでしょう。ライブラリ固有のシンタックスではないので、むしろ自由度が高いとも言えます。
無理に上記のようにまとめる必要はないと思いますが、場合によっては一元管理できて良いかもしれません。
URL パラメータ
パスパラメータの取得
パスパラメータの定義方法は、上でも触れましたが Vue Router と同じで、path-to-regexp の仕様に準じます。
<Switch>
<Route path="/users/:id" children={<User />} />
</Switch>
パラメータの取得には、useParams
フックを用います。
import { useParams } from 'react-router-dom';
function User() {
const { id } = useParams();
// ...
}
パラメータ変更の検知
パラメータの変更を検知して、変更があったときのみ何か処理を行いたい場合は、React の組み込みフック useEffect
を利用すればよいでしょう。
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
function User() {
const { id } = useParams();
useEffect(() => {
console.log(`URL param changed: ${id}`);
}, [id]);
// 以下略...
}
クエリパラメータ
次にクエリパラメータです。React Router には、クエリパラメータを取得するための固有のシンタックスは存在しません。location
オブジェクトの search
プロパティにクエリ文字列が入っているので、各自でパースする必要があります。
const location = useLocation();
const search = location.search; // -> "?foo=bar&baz=piyo"
// URLSearchParamsはIE11非対応
const query = new URLSearchParams(search);
query-string などのライブラリを使ってもいいかもしれません。
マニュアルでは、カスタムフックにまとめるアイディアが紹介されています。
function useQuery() {
const location = useLocation();
return new URLSearchParams(location.search);
}
function MyComponent() {
const query = useQuery();
// 以下略...
}
location オブジェクト
すでに登場していますが、location
オブジェクトには、現在のルートに関する情報が詰まっています。
location
オブジェクトを取得するには、useLocation
フックを用います。
const location = useLocation();
location
オブジェクトは以下のプロパティを持っています。
{
key: 'abc123',
pathname: '/path/to/current/page',
search: '?foo=bar&baz=piyo',
hash: '#some-hash',
state: {
one: true,
two: 'string',
three: { four: 5 }
}
}
state
は Link
や Redirect
、またプログラムによるナビゲーション時に付与することのできるユーザー定義のデータです。
リダイレクト
リダイレクトには、Redirect
コンポーネントを使用します。
以下の例では、authenticated
が true
の場合には /mypage
に紐づくコンポーネント(Route
で定義されている前提)が描画されます。
<Route exact path="/">
{authenticated ? <Redirect to="/mypage" /> : <Home />}
</Route>
Route
コンポーネントと同様に、to
プロパティにはオブジェクトも指定できます。
Switch
の中で使用する際は、from
プロパティを用います。
<Switch>
<Redirect from="/from-path" to="/to-path" />
<Route path="/to-path" children={<MyComponent />} />
</Switch>
ナビゲーションガード
前述の通り、React Router では Vue Router のようなナビゲーションガードの機能は提供されていません。ここでは、よくありそうな、ナビゲーション前後に処理を加えたいユースケースについて、React Router での解決策を紹介します。
遷移先コンポーネント描画前
まずは遷移先コンポーネント描画前に処理を加えるパターンです。
ログイン済みのユーザーしか閲覧できないコンテンツを描画する前に認証チェックする、などがあるあるなユースケースでしょう。
そういった場合、Route
コンポーネントをラップしたコンポーネントを作成して対応します。
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
// 特定のルートで前処理を加えるラッパーコンポーネント
function GuardedRoute(props) {
// ここで何らかの前処理を行う
// たとえば条件によるリダイレクトなど
if (notAuthenticated) {
return <Redirect to="/login" />
}
// 前処理が終わったあと、普通にRouteをレンダリングする
// {...props} はプロパティをそのまま展開するための記述
return <Route {...props} />;
}
export default GuardedRoute;
前処理を加えたいルートに対しては、ラッパーコンポーネントを使用します。
<Switch>
<Route path="/cat" children={<Cat />} />
<GuardedRoute path="/dog" children={<Dog />} />
<GuardedRoute path="/bird" children={<Bird />} />
</Switch>
上の例だと、/cat
に対しては前処理は行われず、/dog
と /bird
のルートでは前処理が行われます。React ではこういったケースもコンポーネントで対処するのですね。
別の前処理が必要な場合は、別のラッパーコンポーネントを作成すればOKです。
遷移直前
遷移直前に処理を行う場合に関しては、何かの条件で「ページを離れますがよろしいですか?」 などの確認ダイアログを出す機能が、React Router から提供されています。
あるページコンポーネントに Prompt
コンポーネントを加えると、そのコンポーネントを離れる直前にブラウザの確認ダイアログが表示されます。
return (
<>
<Prompt
when={condition}
message="入力した内容が保存されていません。このページから離れますか?"
/>
<form>...</form>
</>
);
ダイアログで「OK」をクリックするとナビゲーションが走り、「キャンセル」をクリックするとナビゲーションが中止されます。
when
プロパティにダイアログが表示される条件(Boolean
)を指定します。when
を置かない場合は必ずダイアログが表示されます。
プログラムによるナビゲーション
Link
からではなく、動的にナビゲーションを発生させるには、history
オブジェクトを使用します。
history
オブジェクトは、useHistory
フックから取得します。
import { useHistory } from 'react-router-dom';
const history = useHistory();
history.push('/about'); // /aboutへのナビゲーションが発生
history
オブジェクトは History API と似たインターフェースを持っています。
history.push('/about', { someState: 'foo' });
history.replace('/about');
history.replace('/about', { someState: 'foo' });
history.go(2);
history.goBack(); // = go(-1)
history.goForward(); // = go(1)
さらに、前述の Prompt
の機能を動的に呼び出すことも可能です。
const unblock = history.block('このページを離れますか?');
unblock(); // ブロック解除 = ナビゲーション発生
404ページ
どこにもマッチしなかった場合に Not Found 的な画面を見せたい場合は、以下のように path
にワイルドカード(*
)を使った指定で実現できます。
<Switch>
<Route path="/cat" children={<Cat />} />
<Route path="/dog" children={<Dog />} />
<Route path="*" children={<NotFound />} />
</Switch>
ワイルドカードルートは、Switch
内で一番下に定義されていなければいけません。上から順番に見ていって、マッチするルートがなかった場合は、最後に必ずワイルドカードルートにマッチするという仕組みです。
スクロールの振る舞い
最後に、ページ遷移時にスクロール位置を上部に戻る方法を紹介します。
マニュアルで紹介されているままですが、パスの変更を検知してスクロール位置を変更するコンポーネントを作成します。
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
function ScrollTop() {
const { pathname } = useLocation();
// パス変更時に画面トップへスクロール
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null; // 何もレンダリングしない
}
export default ScrollTop;
特に何もレンダリングしないコンポーネントは、null
を返すようです。
Router
のすぐ下に配置します。
<BrowserRouter>
<ScrollTop />
<Switch>
<Route path="..." children={...} />
<Route path="..." children={...} />
<Route path="..." children={...} />
</Switch>
</BrowserRouter>
React ではなんでもコンポーネントですね。
以上、React Router の基本機能について紹介しました。ここで紹介した機能だけでも使い始められると思いますが、より細かいコンポーネントのプロパティや仕様について知る必要が出た際は、ぜひマニュアルを参照してください。
『Vue.jsエンジニアのためのReact入門』でも思いましたが、やはり React 勢はシンプル、というか Vue 関連のほうが親切な機能が最初からいっぱい付いてるって感じですね。
この記事が React 学習の一助となれば幸いです