React 入門記事、第2弾。
前回は基礎的なことをまとめましたが、今回は React アプリのルーティング設定をするうえでよく使われているReact Router
についてまとめました。
※この記事は元々 Qiita からの転載です。
現在は Qiita でなく Zenn の方で更新しています。
目次
React Router とは? #
React で SPA を書くにあたって、DOM を書き換えて複数ページがあるように見せても、URL が変わらずブラウザからは1つのページとしてしか認識されません。
そこで、SPA の画面状態と URL とを紐づけるルーティングを行うことで、history API を操作できるようにします。
そうすることで URL を指定して直接特定の画面にいけたり、ブラウザバックを利用できるようにすることができます。
このルーティングを行うデファクトのライブラリがReact Router
です。
React Router
を使うことで history API を操作して、画面遷移も行ってくれます。
インストール #
$ yarn add react-router-dom
今回の使用バージョンは5.1.2
です。
使い方 #
以下のコードは公式ドキュメントをコードを引用、もしくは元にしています。
React Router
※2020/2/22 追記
以下の記事を参考に、React Hooks を使ったやり方を記述するなど、全体的に記事を見直しました。
基本的な使い方 #
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from 'react-router-dom';
const App = () => {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to='/'>Home</Link>
</li>
<li>
<Link to='/about'>About</Link>
</li>
<li>
<Link to='/users'>Users</Link>
</li>
</ul>
</nav>
<Switch>
<Route path='/about'>
<About />
</Route>
<Route path='/users'>
<Users />
</Route>
<Route path='/'>
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
const Home = () => {
return <h2>Home</h2>;
}
const About = () => {
return <h2>About</h2>;
}
const Users = () => {
return <h2>Users</h2>;
}
export default App;
ルーティングに必要なものを import #
ルーティングに最低限必要なコンポーネントを import します。
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from 'react-router-dom';
ルート階層でコンポーネント全体をBrowserRouter
で囲う #
ここではRouter
に命名しています。
このBrowserRouter
コンポーネントは、画面遷移の際、ヒストリー API に履歴情報を追加してくれるものです。
const App = () => {
return (
<Router>
.
.
.
</Router>
)
}
URL ごとのレンダリング内容の定義 #
Switch
コンポーネントで囲い、Route
コンポーネントでそれぞれの URL に応じたレンダリング内容を記述します。
上から順に URL と path を比較し、一致するルートの内容を返します。
注意点として通常では完全一致で比較しません。
例として path が/about
の場合、/about/a
などでも一致とみなされます。
完全一致にしたい場合は下記参照。
<Switch>
<Route path='/about'>
<About />
</Route>
<Route path='/users'>
<Users />
</Route>
<Route path='/'>
<Home />
</Route>
</Switch>
.
.
.
const Home = () => {
return <h2>Home</h2>;
}
const About = () => {
return <h2>About</h2>;
}
const Users = () => {
return <h2>Users</h2>;
}
なお、Route の path は一度に複数定義も可能です。
以下の場合は、/about
、/profile
両方のパスで About コンポーネントをレンダリングします。
<Route path={['/about', '/profile']}>
<About />
</Route>
リンクの作成 #
Link コンポーネントで画面遷移するリンクを作成。to でリンク URL を指定。
後に a タグに変換されるようになっており、外部リンクも指定可能です。
なお、この例では一つのコンポーネント内に共存していますが、Link コンポーネントは Switch、Route コンポーネントを使用しているコンポーネント内でしか使用できないということはありません。
<nav>
<ul>
<li>
<Link to='/'>Home</Link>
</li>
<li>
<Link to='/about'>About</Link>
</li>
<li>
<Link to='/users'>Users</Link>
</li>
</ul>
</nav>
これで基本的なルーティングが作成できます。
レンダリング内容で別ファイルに切り出したコンポーネントを使いたい #
コンポーネントを import して、Route の component で渡してあげれば OK です。
import About from './component/About';
import Users from './component/Users';
import Home from './component/Home';
.
.
.
<Switch>
<Route path='/about' component={About} />
<Route path='/users' component={Users} />
<Route path='/' component={Home} />
</Switch>
より正確なルーティングにしたい #
URL と path の完全一致にしたい場合は、Route に exact をつけます。
<Switch>
<Route path='/about' exact>
<About />
</Route>
<Route path='/users' exact>
<Users />
</Route>
<Route path='/' exact>
<Home />
</Route>
</Switch>
定義していない URL にアクセスされた場合のレンダリング内容を指定したい #
Switch の末尾に Route を追加。
path を指定しない Route を最後に記述しておくことで対応できます。
<Switch>
<Route path='/about'>
<About />
</Route>
<Route path='/users'>
<Users />
</Route>
<Route>
<Error />
</Route>
</Switch>
パスパラメータを使いたい #
URL のパスパラメータを受け付けるようにする場合は、Route の path で:aboutId
のように:
をつけて指定します。
そして、レンダリング内容の方でuseParams
フックを使用して取り出すことができます。
以下の場合、useParams()
の返り値は{aboutId: "1"}
となり、分割代入 + ショートハンドを使用して変数に代入しています。
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams
} from 'react-router-dom';
.
.
.
<Link to='/about/1'>About</Link>
.
.
.
<Switch>
<Route path='/about/:aboutId'>
<About />
</Route>
</Switch>
const About = () => {
const { aboutId } = useParams();
return <h2>About:{aboutId}</h2>
}
クエリパラメータや任意のデータを使いたい #
Link コンポーネントのtoには、オブジェクト形式でデータを渡すことができます。
- pathname:URL
- search:クエリパラメータ
- hash:URL ハッシュ
- state:ユーザ定義のデータ
レンダリング内容の方でuseLocation
フックを使用して、location オブジェクトとして取り出しが可能です。
以下の場合、useLocation()
の返り値は次のようになります。
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useLocation
} from 'react-router-dom';
.
.
.
const to = {
pathname: '/users',
search: '?class=A',
hash: '#user-hash',
state: { test: 'test-state' }
};
.
.
.
<Link to={to}>Users</Link>
.
.
.
<Switch>
<Route path='/users'>
<Users />
</Route>
</Switch>
const Users = () => {
const location = useLocation();
return (
<React.Fragment>
<h2>Users</h2>
<p>pathname:{location.pathname}</p>
<p>search:{location.search}</p>
<p>hash:{location.hash}</p>
<p>state:{location.state.test}</p>
</React.Fragment>
);
}
ちなみにクエリパラメータを取り出して扱いたい場合は、query-string
というライブラリを使うと便利です。
上記のコード例においてuseLocation
で取り出した、location オブジェクトのクエリパラメータに対して
import queryString from 'query-string';
.
.
.
queryString.parse(location.search)
とすると、{class: "A"}
のようにオブジェクト形式に変換してくれるため、扱いやすくなります。
query-string
を頻繁に使う場合は、あらかじめuseLocation
とquery-string
を組み合わせたカスタムフックを作っておく手もありです。
Link コンポーネントを使わずに遷移させたい #
ボタンの onClick アクションなどで画面遷移させたい時は、history オブジェクトを使用します。
useHistory
フックを使用することで、history オブジェクトの取り出しが可能です。
以下はナビゲーション用のコンポーネントを作成した例です。
history.push
を使用して、履歴を追加することで URL を変化させ画面遷移を実現しています。
※a タグとは違うので、リンクを右クリックして別タブで開くといったことはできません。
import React from 'react';
import { useHistory } from 'react-router-dom';
const Nav = props => {
const history = useHistory();
const handleLink = path => history.push(path);
return (
<nav>
<button onClick={() => handleLink('/about')}>About</button>
<button onClick={() => handleLink('/users')}>Users</button>
<button onClick={() => handleLink('/')}>Home</button>
</nav>
);
}
export default Nav;
historyオブジェクトでできること #
history オブジェクトの中身は以下のようになっており、location オブジェクトも含まれています。
※ history.push 後の例です。
history オブジェクトは History API と完全一致ではありませんが、似たような処理ができます。
// 履歴の追加
history.push('/about')
// 履歴の追加 + ユーザ定義データの受け渡し
history.push('/about', { someState: 'foo' });
// 履歴の書き換え
history.replace('/about');
// 履歴の書き換え + ユーザ定義データの受け渡し
history.replace('/about', { someState: 'foo' });
// 履歴を2つ進める
history.go(2);
// 履歴を1つ戻る
history.goBack();
// 履歴を1つ進める
history.goForward();
// 上記の履歴変更の前に記述しておくと、遷移前にアラートを出す
history.block('このページを離れますか?');
ネストしたルーティングを実現したい #
useRouteMatch
フックで取得できる、match オブジェクトを使用することで実現できます。
なお、match オブジェクトの中身は次のようになります。
※以下のコードで Topics コンポーネントをレンダリング時の例
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useRouteMatch
} from 'react-router-dom';
const App = () => {
return (
<Router>
<div>
<ul>
<li>
<Link to='/'>Home</Link>
</li>
<li>
<Link to='/topics'>Topics</Link>
</li>
</ul>
<Switch>
<Route path='/topics'>
<Topics />
</Route>
<Route path='/'>
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
const Home = () => {
return <h2>Home</h2>;
}
const Topics = () => {
const match = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<Switch>
<Route path={`${match.path}/components`}>
<h3>Components</h3>
</Route>
<Route path={`${match.path}/props-v-state`}>
<h3>props-v-state</h3>
</Route>
</Switch>
</div>
);
}
export default App;
リダイレクト #
Redirect コンポーネントを使用して、リダイレクトが実現できます。
以下はauthenticated
変数によって遷移する先を変化させている例で、ログインしている場合はマイページ、していない場合は通常のホーム画面といった感じです。
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect
} from 'react-router-dom'
.
.
.
<Switch>
<Route path='/mypage'>
<Mypage />
</Route>
<Route path='/'>
{authenticated ? <Redirect to="/mypage" /> : <Home />}
</Route>
</Switch>
また、以下のような書き方もできます。
こちらの場合はfrom
を使用していて、from
の URL にアクセスされたら、to
の URL にリダイレクトします。
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect
} from 'react-router-dom'
.
.
.
<Switch>
<Redirect from='/test' to='/other' />
<Route path='/other'>
<Other />
</Route>
.
.
.
</Switch>
※旧式:HOC での location、history、match オブジェクトの取り扱い #
上記で書いていた、use〇〇
フックは React Hooks の一種です。
React Hooks が登場する前は HOC ベースのやり方が普及しており、こちらでは主にwith〇〇
というものでコンポーネントをラップしたりするやり方でした。
React Router においてはwithRouter
というものがあります。
これでコンポーネントをラップすることで、そのコンポーネントの props にmatch、location、history オブジェクトが渡され、操作できるようになります。
以下は history オブジェクトを使用する例です。
import React from 'react';
import { withRouter } from 'react-router';
const HistoryNav = props => {
const handleLink = path => props.history.push(path);
return (
<nav>
<button onClick={() => handleLink('/about')}>About</button>
<button onClick={() => handleLink('/users')}>Users</button>
<button onClick={() => handleLink('/')}>Home</button>
</nav>
);
}
export default withRouter(HistoryNav);
参考リンクまとめ #
- React Router
- react-routerのページ遷移をhandleで行う時にはwithRouterを使う
- react-router@v4を使ってみよう:シンプルなtutorial
- Hookにも対応!Vue.jsエンジニアのためのReact Router v5入門
シリーズ記事 #
- React入門 ~基礎編~
- React入門 ~React Router編~ ※当記事
- React入門 ~PropTypes編~
- React入門 ~Material UI編~
- React入門 ~Recompose編~