Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
69
Help us understand the problem. What is going on with this article?
@h-yoshikawa44

React入門 ~React Router編~

React入門記事、第2弾。
前回は基礎的なことをまとめましたが、今回はReactアプリのルーティング設定を行ううえでよく使われているReact Routerについてまとめました。


※2020/09/22追記
この記事は Zenn に転載しました。
以降の更新は 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を使ったやり方を記述するなど、全体的に記事を見直しました。
- Hookにも対応!Vue.jsエンジニアのためのReact Router v5入門

基本的な使い方

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;

router-standard.gif

ルーティングに必要なものを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>
  }

router-useparam.gif

クエリパラメータや任意のデータを使いたい

Linkコンポーネントのtoには、オブジェクト形式でデータを渡すことができます。
- pathname:URL
- search:クエリパラメータ
- hash:URLハッシュ
- state:ユーザ定義のデータ

レンダリング内容の方でuseLocationフックを使用して、locationオブジェクトとして取り出しが可能です。
以下の場合、useLocation()の返り値は次のようになります。
location.png

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>
  );
}

router-uselocation.gif

ちなみにクエリパラメータを取り出して扱いたい場合は、query-stringというライブラリを使うと便利です。
上記のコード例においてuseLocationで取り出した、locationオブジェクトのクエリパラメータに対して

import queryString from 'query-string';
.
.
.
queryString.parse(location.search)

とすると、{class: "A"}のようにオブジェクト形式に変換してくれるため、扱いやすくなります。

query-stringを頻繁に使う場合は、あらかじめuseLocationquery-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;

router-usehistory.gif

historyオブジェクトでできること

historyオブジェクトの中身は以下のようになっており、locationオブジェクトも含まれています。
※history.push後の例です。
history.png

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コンポーネントをレンダリング時の例
match.png

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;

router-nest.gif

リダイレクト

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);

参考リンクまとめ

69
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
h-yoshikawa44
精神疾患持ちのWebプログラマー。 (リンクは個人ブログです) ※2020/09よりZennをメインにしていく関係上、Qiita記事の更新・投稿は基本的に停止していきます。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
69
Help us understand the problem. What is going on with this article?