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 Homepost-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 PostDetailposts.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 Postssign-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動作確認
上記コードの動作確認をします。

起動するとこのような画面が表示されます。
URLの完全一致|exact
App.js 内にて、 URL( / ) と Homeコンポーネント を紐づける記述にて、exact を指定しています。
<Route exact path="/" component={Home}/>exact を指定することで、URLが完全一致したときだけ条件が満たされるようになります。

試しに exactを取り除いてみると、URLが/signinなのに前方一致してしまい、Homeコンポーネントが表示されてしまいます。
ページ遷移
Linkコンポーネントを利用
App.js 内にて react-router-dom の Linkコンポーネント を利用してページ遷移を実装しています。
<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>
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>
propsに渡される情報
Routeコンポーネントでレンダリングされた、ページコンポーネントにはpropsが追加されています。
( history location match )

/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}/>)
}/>
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"/>
)}/>
)
}
App.js にて、isAuthenticated = falseに変更して動作確認します。
/posts/posts/:id にアクセスしても /signin にリダイレクトされています。