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
動作確認
上記コードの動作確認をします。
起動するとこのような画面が表示されます。
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
にリダイレクトされています。