Reactで画面遷移をしよう
Reactで画面遷移をすることが多くあるので方法をまとめていきます。
Reactの画面遷移には、React Router
を使うと簡単に作成することができます。
基本的なルーティング設定の書き方と、分割方法などまとめます。
React Routerの導入
create-react-appでプロジェクトを作成した後、はじめにReactRouterのパッケージをインストールします。
$ yarn add react-router-dom
画面遷移するためのページを用意しよう
試しに画面遷移先を作成したいと思います。今回はHomeとPage1〜Page3を用意しました。
export const Home = () => {
return (
<div>
<h1>Home</h1>
</div>
);
};
export const Page1 = () => {
return (
<div>
<h1>Page1</h1>
</div>
);
};
export const Page2 = () => {
return (
<div>
<h1>Page2</h1>
</div>
);
};
export const Page3 = () => {
return (
<div>
<h1>Page3</h1>
</div>
);
};
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
基本的な画面遷移
App.jsxにインストールしたReact Routerを使ってルーティングの設定を追加します。
react-router-dom
のインポートを追加し、BrowserRouter
とLink
とSwitch
とRoute
を使えるようにします。
BrowserRouterでルーティングする設定を囲んであげます。
LinkはHTMLでいうとaタグのようなものです。to=""の中にパスを指定してあげます。
SwitchとRouteはルーティングするコンポーネントを指定します。exactをつけると完全一致になります。Homeはexactをつけてあげます。つけないと、/
配下のすべてが対象になってしまいます。
// react-router-domのインポートを追加
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";
import { Home } from "./components/Home";
import { Page1 } from "./components/Page1";
import { Page2 } from "./components/Page2";
import { Page3 } from "./components/Page3";
function App() {
return (
<BrowserRouter>
<div className="App">
<Link to="/">Home</Link>
<br />
<Link to="/page1">Page1</Link>
<br />
<Link to="/page2">Page2</Link>
<br />
<Link to="/page3">Page3</Link>
<br />
<Switch>
{/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
<Route exact path="/">
<Home />
</Route>
<Route path="/page1">
<Page1 />
</Route>
<Route path="/page2">
<Page2 />
</Route>
<Route path="/page3">
<Page3 />
</Route>
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
簡単に画面遷移させることができるようになりました。
ネストされたページ遷移
先程作成したPage1からネストされたページ遷移を作成したいと思います。今回は、Page1DetailAとPage1DetailBのコンポーネントを2つ用意しました。
export const Page1DetailA = () => {
return (
<div>
<h1>Page1DetailA</h1>
</div>
);
};
export const Page1DetailB = () => {
return (
<div>
<h1>Page1DetailB</h1>
</div>
);
};
Page1にimport { Link } from "react-router-dom"
を追加し、遷移先のリンクを記載します。
import { Link } from "react-router-dom";
export const Page1 = () => {
return (
<div>
<h1>Page1</h1>
<Link to="/page1/detailA">DetailA</Link>
<br />
<Link to="/page1/detailB">DetailB</Link>
</div>
);
};
最後にルーティング設定を追加します。
// react-router-domのインポートを追加
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";
import { Home } from "./components/Home";
import { Page1 } from "./components/Page1";
import { Page2 } from "./components/Page2";
import { Page3 } from "./components/Page3";
import { Page1DetailA } from "./components/Page1DetailA";
import { Page1DetailB } from "./components/Page1DetailB";
function App() {
return (
<BrowserRouter>
<div className="App">
<Link to="/">Home</Link>
<br />
<Link to="/page1">Page1</Link>
<br />
<Link to="/page2">Page2</Link>
<br />
<Link to="/page3">Page3</Link>
<br />
<Switch>
{/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
<Route exact path="/">
<Home />
</Route>
<Route
path="/page1"
render={() => (
<Switch>
<Route exact path="/page1">
<Page1 />
</Route>
<Route exact path="/page1/detailA">
<Page1DetailA />
</Route>
<Route exact path="/page1/detailB">
<Page1DetailB />
</Route>
</Switch>
)}
/>
<Route path="/page2">
<Page2 />
</Route>
<Route path="/page3">
<Page3 />
</Route>
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
実行し、確認します。ネストされたページ遷移ができました。
page1を複数書くと混乱につながるので、propsで受け取ったurlを使うように変更すると、下記のように変更することができます。
// react-router-domのインポートを追加
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";
import { Home } from "./components/Home";
import { Page1 } from "./components/Page1";
import { Page2 } from "./components/Page2";
import { Page3 } from "./components/Page3";
import { Page1DetailA } from "./components/Page1DetailA";
import { Page1DetailB } from "./components/Page1DetailB";
function App() {
return (
<BrowserRouter>
<div className="App">
<Link to="/">Home</Link>
<br />
<Link to="/page1">Page1</Link>
<br />
<Link to="/page2">Page2</Link>
<br />
<Link to="/page3">Page3</Link>
<br />
<Switch>
{/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
<Route exact path="/">
<Home />
</Route>
<Route
path="/page1"
render={({ match: { url } }) => ( // propsでurlを受け取る
<Switch>
<Route exact path={url}>
<Page1 />
</Route>
<Route exact path={`${url}/detailA`}>
<Page1DetailA />
</Route>
<Route exact path={`${url}/detailB`}>
<Page1DetailB />
</Route>
</Switch>
)}
/>
<Route path="/page2">
<Page2 />
</Route>
<Route path="/page3">
<Page3 />
</Route>
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
ルーティング設定の分割
これまでルーティング設定をApp.jsxに書いてきましたが、コードの見通しを良くするためにルーティング設定の分割を行います。
src配下にrouterのフォルダを作成し、Router.jsxのファイルを作成します。これまでApp.jsxに書いていたルーティング設定を移動させます。
import { Switch, Route } from "react-router-dom";
import { Home } from "../Home";
import { Page1 } from "../Page1";
import { Page1DetailA } from "../Page1DetailA";
import { Page1DetailB } from "../Page1DetailB";
import { Page2 } from "../Page2";
import { Page3 } from "../Page3";
export const Router = () => {
return (
<Switch>
{/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
<Route exact path="/">
<Home />
</Route>
<Route
path="/page1"
render={(
{ match: { url } } // propsでurlを受け取る
) => (
<Switch>
<Route exact path={url}>
<Page1 />
</Route>
<Route exact path={`${url}/detailA`}>
<Page1DetailA />
</Route>
<Route exact path={`${url}/detailB`}>
<Page1DetailB />
</Route>
</Switch>
)}
/>
<Route path="/page2">
<Page2 />
</Route>
<Route path="/page3">
<Page3 />
</Route>
</Switch>
);
};
// react-router-domのインポートを追加
import { BrowserRouter, Link } from "react-router-dom";
import { Router } from "./components/router/Router";
function App() {
return (
<BrowserRouter>
<div className="App">
<Link to="/">Home</Link>
<br />
<Link to="/page1">Page1</Link>
<br />
<Link to="/page2">Page2</Link>
<br />
<Link to="/page3">Page3</Link>
<br />
</div>
<Router />
</BrowserRouter>
);
}
export default App;
無事分けることができました!
実行して動作することが確認できました。
更にページごとに分割してみる
page1をさらに分割してみます。Page1Routes.jsxというファイルを作成し、オブジェクトとしてルーティング設定を記載します。
import { Page1 } from "../Page1";
import { Page1DetailA } from "../Page1DetailA";
import { Page1DetailB } from "../Page1DetailB";
export const page1Routes = [
{
path: "/",
exact: true,
children: <Page1 />,
},
{
path: "/detailA",
exact: false,
children: <Page1DetailA />,
},
{
path: "/detailB",
exact: false,
children: <Page1DetailB />,
},
];
import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { Page2 } from "../Page2";
import { Page3 } from "../Page3";
import { page1Routes } from "./Page1Routes";
export const Router = () => {
return (
<Switch>
{/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
<Route exact path="/">
<Home />
</Route>
<Route
path="/page1"
render={({ match: { url } }) => (
<Switch>
{page1Routes.map((route) => (
<Route
key={route.path}
exact={route.exact}
path={`${url}${route.path}`}
>
{route.children}
</Route>
))}
</Switch>
)}
/>
<Route path="/page2">
<Page2 />
</Route>
<Route path="/page3">
<Page3 />
</Route>
</Switch>
);
};
無事分けることができました。ここまで分割すると、見通しが良くなりますね。
URLパラメーターを扱う
import { useParams } from "react-router-dom";
を使うことで、URLパラメーターを使うことができます。
Page2をURLパラメーターを使うように変更していきます。
pathに:id
のように:
を使うことでパラメーターを扱うことができます。
import { Page2 } from "../Page2";
import { UrlParameter } from "../UrlParameter";
export const page2Routes = [
{
path: "/",
exact: true,
children: <Page2 />,
},
{
path: "/:id", //URLパラメーターを使う
exact: false,
children: <UrlParameter />,
},
];
useParams
をインポートし、idを受け取るように変更します。
import { useParams } from "react-router-dom";
export const UrlParameter = () => {
const { id } = useParams(); //URLパラメーターのIDを受け取る
return (
<div>
<h1>UrlParameter</h1>
<p>パラメーターは{id}です</p>
</div>
);
};
Linkでパラメーターの100を渡すように変更します。
import { Link } from "react-router-dom";
export const Page2 = () => {
return (
<div>
<h1>Page2</h1>
<Link to="/page2/100">URL Parameter</Link>
</div>
);
};
import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { Page3 } from "../Page3";
import { page1Routes } from "./Page1Routes";
import { page2Routes } from "./Page2Routes";
export const Router = () => {
return (
<Switch>
{/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
<Route exact path="/">
<Home />
</Route>
<Route
path="/page1"
render={({ match: { url } }) => (
<Switch>
{page1Routes.map((route) => (
<Route
key={route.path}
exact={route.exact}
path={`${url}${route.path}`}
>
{route.children}
</Route>
))}
</Switch>
)}
/>
<Route
path="/page2"
render={({ match: { url } }) => (
<Switch>
{page2Routes.map((route) => (
<Route
key={route.path}
exact={route.exact}
path={`${url}${route.path}`}
>
{route.children}
</Route>
))}
</Switch>
)}
/>
<Route path="/page3">
<Page3 />
</Route>
</Switch>
);
};
パラメーターを受け取ることができるようになりました。
クエリパラメータを扱う
import { useLocation } from "react-router-dom";
をインポートすることクエリパラメータを扱うことができます。const { search } = useLocation();
でクエリパラメータを受け取る事ができます。
では実際に、Page2に書いていきます。
import { useParams, useLocation } from "react-router-dom";
export const UrlParameter = () => {
const { id } = useParams(); //URLパラメーターのIDを受け取る
const { search } = useLocation(); // クエリパラメータを受け取る
const query = new URLSearchParams(search);
return (
<div>
<h1>UrlParameter</h1>
<p>パラメーターは{id}です</p>
<p>クエリは{query.get("name")}です</p>
</div>
);
};
queryページへのリンクを追加します。
import { Link } from "react-router-dom";
export const Page2 = () => {
return (
<div>
<h1>Page2</h1>
<Link to="/page2/100">URL Parameter</Link>
<br />
<Link to="/page2/100?name=hoge">Query Parameter</Link>
</div>
);
};
ページ遷移する際に状態を渡す
Linkのtoの箇所をオブジェクトで書くことができ、stateに状態を渡すことができます。
import { Link } from "react-router-dom";
export const Page1 = () => {
const arr = [...Array(100).keys()]; // 受け渡し用のダミーデータ作成
return (
<div>
<h1>Page1</h1>
<Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>
<br />
<Link to="/page1/detailB">DetailB</Link>
</div>
);
};
const { state } = useLocation();
で渡された状態を取得することができます。
import { useLocation } from "react-router-dom";
export const Page1DetailA = () => {
const { state } = useLocation();
console.log(state);
return (
<div>
<h1>Page1DetailA</h1>
</div>
);
};
無事状態が渡されていることが確認できました。
useHistoryを使って画面遷移
useHistoryを使うことで、Linkを使わずに画面遷移させることができます。
import { Link, useHistory } from "react-router-dom";
export const Page1 = () => {
const arr = [...Array(100).keys()]; // 受け渡し用のダミーデータ作成
const history = useHistory(); // historyを用意する
const onClickButton = () => {
history.push("/page1/detailA"); // 画面遷移を書く
};
return (
<div>
<h1>Page1</h1>
<Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>
<br />
<Link to="/page1/detailB">DetailB</Link>
<br />
<button onClick={onClickButton}>DetailA</button>
</div>
);
};
戻るときは、goback()
を使います。
import { useLocation, useHistory } from "react-router-dom";
export const Page1DetailA = () => {
const { state } = useLocation();
const history = useHistory(); // historyを用意する
const onClickButton = () => {
history.goBack(); // 戻ることができる
};
return (
<div>
<h1>Page1DetailA</h1>
<button onClick={onClickButton}>戻る</button>
</div>
);
};
404ページを作成してみる
はじめに404ページを作成します。
export const Page404 = () => {
return (
<div>
<h1>Page404</h1>
</div>
);
};
ルーティング設定の一番下に、path="*"
を追加し、pathに一致しなかった場合すべて404ページが表示されるようします。
import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { Page3 } from "../Page3";
import { Page404 } from "../Page404";
import { page1Routes } from "./Page1Routes";
import { page2Routes } from "./Page2Routes";
export const Router = () => {
return (
<Switch>
{/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
<Route exact path="/">
<Home />
</Route>
<Route
path="/page1"
render={({ match: { url } }) => (
<Switch>
{page1Routes.map((route) => (
<Route
key={route.path}
exact={route.exact}
path={`${url}${route.path}`}
>
{route.children}
</Route>
))}
</Switch>
)}
/>
<Route
path="/page2"
render={({ match: { url } }) => (
<Switch>
{page2Routes.map((route) => (
<Route
key={route.path}
exact={route.exact}
path={`${url}${route.path}`}
>
{route.children}
</Route>
))}
</Switch>
)}
/>
<Route path="/page3">
<Page3 />
</Route>
{/* 404ページ追加 */}
<Route path="*">
<Page404 />
</Route>
</Switch>
);
};
404ページが表示されるようになりました。