React Routerでページ制御

React Routerを利用して、URLに応じて利用するページコンポーネントを切り替える方法を確認します。「URLパラメータを取得する方法」「非公開ページにする方法」など取り上げます。

環境

create-react-app でプロジェクト生成後、react-router-dom をインストールしてます。

npm install react-router-dom --save 
$ cat package.json | awk '/dependencies/,/}/'
  "dependencies": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-router-dom": "^5.1.2",
    "react-scripts": "3.2.0"
  },

動作確認用コード

フォルダ構成

今回は、以下フォルダ構成で動作確認を行います。

src/
├── index.js                                    // DOMにReact要素(App)を反映
├── App.js                                      // React Routerでページコンポーネントを切り替え
└── pages
    ├── home
    │   └── home.component.jsx                  // ログイン状態に関わらずアクセス可能
    ├── post-detail
    │   └── post-detail.component.jsx           // 未ログインであればアクセスさせない(sign-in-and-sign-upにリダイレクトさせる)
    ├── posts
    │   └── posts.component.jsx                 // 未ログインであればアクセスさせない(sign-in-and-sign-upにリダイレクトさせる)
    └── sign-in-and-sign-up
        └── sign-in-adn-sign-up.component.jsx   // ログイン中であればアクセスさせない(homeにリダイレクトさせる)

ページコンポーネント( 4つ )

pagesフォルダ 配下に4つのページコンポーネントを追加します。

home.component.jsx

import React from 'react'

const Home = props => (
  <div>
    <h2>Home</h2>
    <p>{`history: ${JSON.stringify(props.history, null, '\t')}`}</p>
    <p>{`location: ${JSON.stringify(props.location, null, '\t')}`}</p>
    <p>{`match: ${JSON.stringify(props.match, null, '\t')}`}</p>
  </div>
)

export default Home

post-detail.component.jsx

import React from 'react'

const PostDetail = props => (
  <div>
    <h2>PostDetail</h2>
    <p>{`history: ${JSON.stringify(props.history, null, '\t')}`}</p>
    <p>{`location: ${JSON.stringify(props.location, null, '\t')}`}</p>
    <p>{`match: ${JSON.stringify(props.match, null, '\t')}`}</p>
  </div>
)

export default PostDetail

posts.component.jsx

import React from 'react'

const Posts = props => (
  <div>
    <h2>Posts</h2>
    <p>{`history: ${JSON.stringify(props.history, null, '\t')}`}</p>
    <p>{`location: ${JSON.stringify(props.location, null, '\t')}`}</p>
    <p>{`match: ${JSON.stringify(props.match, null, '\t')}`}</p>
    <hr/>
    <button onClick={() => props.history.push(`${props.match.url}/1`)}>1</button>
    <button onClick={() => props.history.push(`${props.match.url}/2`)}>2</button>
  </div>
)

export default Posts

sign-in-adn-sign-up.component.jsx

import React from 'react'

const SignInAndSignUpPage = props => (
  <div>
    <h2>SignInAndSignUpPage</h2>
    <p>{`history: ${JSON.stringify(props.history, null, '\t')}`}</p>
    <p>{`location: ${JSON.stringify(props.location, null, '\t')}`}</p>
    <p>{`match: ${JSON.stringify(props.match, null, '\t')}`}</p>
  </div>
)

export default SignInAndSignUpPage

ページコンポーネントの切り替え

App.js

App.js にURLに応じてページコンポーネントを切り替える制御を記述します。

import React from 'react'
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
  Redirect
} from 'react-router-dom'

import Home from './pages/home/home.component'
import Posts from './pages/posts/posts.component'
import PostDetail from './pages/post-detail/post-detail.component'
import SignInAndSignUpPage from './pages/sign-in-and-sign-up/sign-in-adn-sign-up.component'

const isAuthenticated = true

const App = () => {
  return (
    <div className="App">
      <p>{`isAuthenticated: ${isAuthenticated}`}</p>
      <Router>
        <div>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/signin">SingIn</Link></li>
            <li><Link to="/posts">Posts</Link></li>
            <li><Link to="/posts/1">Posts > 1</Link></li>
            <li><Link to="/posts/2">Posts > 2</Link></li>
          </ul>

          <hr/>

          <Switch>
            <Route exact path="/" component={Home}/>
            <Route exact path="/signin" render={props => isAuthenticated
              ? (<Redirect to="/"/>)
              : (<SignInAndSignUpPage {...props}/>)
            }/>
            <PrivateRoute exact path="/posts" component={Posts}/>
            <PrivateRoute exact path="/posts/:id" component={PostDetail}/>
          </Switch>
        </div>
      </Router>
    </div>
  )
}

const PrivateRoute = ({ component: Component, ...rest }) => {
  return (
    <Route {...rest} render={props => (isAuthenticated
        ? <Component {...props} />
        : <Redirect to="/signin"/>
    )}/>
  )
}

export default App

動作確認

上記コードの動作確認をします。

733-react-router_01_home.png

起動するとこのような画面が表示されます。

URLの完全一致|exact

App.js 内にて、 URL( / )Homeコンポーネント を紐づける記述にて、exact を指定しています。

<Route exact path="/" component={Home}/>

exact を指定することで、URLが完全一致したときだけ条件が満たされるようになります。

733-react-router_home_10_exact.png

試しに exactを取り除いてみると、URLが/signinなのに前方一致してしまい、Homeコンポーネントが表示されてしまいます。

ページ遷移

Linkコンポーネントを利用

App.js 内にて react-router-domLinkコンポーネント を利用してページ遷移を実装しています。

          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/signin">SingIn</Link></li>
            <li><Link to="/posts">Posts</Link></li>
            <li><Link to="/posts/1">Posts > 1</Link></li>
            <li><Link to="/posts/2">Posts > 2</Link></li>
          </ul>
733-react-router_home_20_link.gif

push関数を利用

posts.component.jsx 内にて historyオブジェクトがもつpush関数を利用してページ遷移を実装しています。

    <button onClick={() => props.history.push(`${props.match.url}/1`)}>1</button>
    <button onClick={() => props.history.push(`${props.match.url}/2`)}>2</button>
733-react-router_home_21_link.gif

propsに渡される情報

Routeコンポーネントでレンダリングされた、ページコンポーネントにはpropsが追加されています。
( history location match )

733-react-router_30_props.png

/posts/1でアクセスしたときのPostDetailコンポーネントのpropsです。

URLパラメータの取得

App.js 内にて /posts/:id のときに PostDetailコンポーネント を利用するように指定しています。

<PrivateRoute exact path="/posts/:id" component={PostDetail}/>

:id の値は PostDetailコンポーネントprops.match.params.id を通じて取得できます。

ログインページ
( ログイン済みであればリダイレクト )

App.js 内にて /signin のときにレンダリングするコンポーネントは isAuthenticated の状態に応じて変更するようにしています。

            <Route exact path="/signin" render={props => isAuthenticated
              ? (<Redirect to="/"/>)
              : (<SignInAndSignUpPage {...props}/>)
            }/>
733-react-router_40_signin.gif

isAuthenticated = trueの状態であるため、/signinにアクセスしても/にRedirectされています。

非公開ページ
( 未ログインであればリダイレクト )

App.js 内にて、未ログイン( isAuthenticated = false ) の場合、/posts /posts/:id にアクセスすると /signin にリダイレクトするようにしています。

            <PrivateRoute exact path="/posts" component={Posts}/>
            <PrivateRoute exact path="/posts/:id" component={PostDetail}/>
const PrivateRoute = ({ component: Component, ...rest }) => {
  return (
    <Route {...rest} render={props => (isAuthenticated
        ? <Component {...props} />
        : <Redirect to="/signin"/>
    )}/>
  )
}
733-react-router_50_private.gif

App.js にて、isAuthenticated = falseに変更して動作確認します。

/posts /posts/:id にアクセスしても /signin にリダイレクトされています。

参考