ささっと学ぶReact Router v4
目次
記事としては旬な時期をとっくにすぎていますが、ブログ内にReactやReduxのチュートリアルがあるのにReact Rotuerの説明がないので、必要そうなものだけ書いときます。
React Routerとは
Reactでアプリケーションを作成する上で必須とも言えるのがReact Routerです。React Routerは名前の通り、Reactでのルーティングを担当するライブラリです。
大雑把に言えば、URLの変化に合わせて何か処理をするにはReact Routerが必要なのです。
インストール
React Routerは3つのパッケージがあります。
- react-router
- react-router-dom
- react-router-native
ブラウザで使う場合は、react-router-domを使います。
yarn add react-router-dom
ルーティング導入
React RouterはBrowserRouter
かHashRouter
というコンポーネントを使います。前者はHTML5のhistory API
を使っており、後者はwindow.location
を利用しています。古いブラウザもサポートしなければならない場合は、後者を利用する必要があります。
例えば、Appコンポーネントの中でルーティングを行う場合は以下になります。
// index.js
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./App";
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById("root")
);
実際のルーティング内容はBrowserRouter
で囲ったコンポーネント内に記述します。
ルーティング基礎
シンプルなルーティング例を書くと以下になります。
import React from "react";
import { Link, Route } from "react-router-dom"
const Home = () => <h2>Home</h2>;
const App = () => <h2>App</h2>
const App = () => {
return (
<div>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</div>
);
};
export default App;
Link
はhtmlのアンカータグのようにto
で指定したURL先への移動に利用します。実際に出力されるときもアンカータグで書き出されます。
Route
はそのルーティング先を描画するコンポーネントです。path
で一致したときに、component
で指定したコンポーネントが描画されます。
HomeリンクをクリックするとHomeコンポーネントがレンダリングされ、AboutをクリックするとAboutコンポーネントが描画されます。
exact
exact
はpath
がURLと正確に一致したときを示します。先のコードからexact
を取り除くと、/about
でAboutコンポーネントだけでなく、Homeコンポーネントも描画されます。
これは/about
に/
が含まれるため、両者ともルーティング先と見なされるためです。React Router 4では、このようにpath
にマッチするすべてのRoute
がルーティング対象となるため、注意が必要です。
render and chidren
コンポーネントの描画には、component
でコンポーネントを指定する以外にも、render
とchildren
を使う方法もあります。render
はインナーレンダリングを行います。
<Route path="/" render={(props) => <h2>Home</h2>} />
children
もrender
と同じでインナーレンダリングを行うのですが、パスにマッチしない場合でもルーティングします。children
の利用には他の解説を先に行う必要があるため、詳細は後述する「routeのカスタマイズ」で行います。
props
上記の例でも定義していますが、レンダリングする関数はReact Routerのpropsを受け取ります(クラスの場合はthis.props
)。これには以下のオブジェクトが格納されています。
- match(
path
に一致したURL情報) - location(主に現在のロケーション情報)
- history(ルーティングした過去の履歴情報)
尚、props
はrender
に限らず、component
やchildren
でも取得可能です。
matchとURLパラメーター
よく利用するのはmatch
でしょう。このオブジェクトは、route
のpath
とURLのロケーションが一致したときに作成されます。
例えばURLに末尾にパラメーターがある場合は、このオブジェクトで取得ができます。
const Drink = ({ match } ) => <p>Drink ID: {match.params.id}</p>;
const App = () => {
return (
<div>
<Link to="/drink/1">coke</Link>
<Link to="/drink/2">tea</Link>
<Link to="/drink/3">coffee</Link>
<Route path="/drink/:id" component={Drink} />
</div>
);
};
match
にはいくつかのプロパティが用意されています。ここで利用したparams
はURLパラメーターを含むオブジェクトです。他にもマッチしたURLを表すurl
、route
のpath
を示すpath
などがあります。
上記でmatch
をコンソール出力すると、以下のオブジェクトが確認できます
{
/* coffeeのリンクをクリックした場合 */
path: "/drink/:id",
url: "/drink/3",
isExact: true,
params: {
id: "3"
}
}
ネストされたルーティング
match
を使うと、ネストされたルーティングを実現しつつハードコーティングを減らせます。
const Food = ({ match }) => {
return (
<div>
<Link to={`${match.url}/ramen`}>ラーメン</Link>
<Link to={`${match.url}/rice`}>ご飯</Link>
<Route
path={`${match.path}/:name`}
render={({ match }) => <h3>{match.params.name}</h3>}
/>
</div>
);
};
const App = () => {
return (
<div>
<Link to="/food">Food</Link>
<Route path="/food" component={Food} />
</div>
);
};
Foodコンポーネントのmatch
には先に解説したようなURL情報を含むオブジェクトが格納されているので、それらを利用してネストするLink
とRoute
を構築しているだけです。
Switch
Switch
は複数のroute
を囲い、グループ化するのに使います。Switch
で囲まれたroute
は、現在のpath
名に一致する最初のroute
のみをレンダリングします。
よく利用されるのは、404ページ(Not Found)です。
<Switch>
<Route exact path="/" render={() => <h2>Home</h2>} />
<Route path="/about" render={() => <h2>About</h2>} />
<Route render={() => <h2>Not Found</h2>} />
</Switch>
path
を記述しないRoute
を定義すると、それはすべてのパスでルーティング対象となります。結果、どのレンダリングの結果にもそのroute
の内容が出力されます。
Swtich
で囲むことにより、内部のroute
のpath
が1つずつ評価され、どれにも該当しなかったときにpath
のないroute
が選ばれることになります。それが404ページ(Not Found)です。
routeのカスタマイズ
render and childrenで保留していたchildren
を使うと、ルーティングをカスタマイズできます。
children
はrender
と使い方は同じですが、path
に一致しない場合でも呼びだされます。このため、path
に一致している・していないときで、レンダリング内容を変えることができます。
const App = () => {
return (
<div>
<Link to="/">Home</Link><br />
<Link to="/about">About</Link><br />
<Route
path="/blog"
children={({ match }) => {
return (
<div>
{match ? ">" : ""}
<Link to="/blog">Blog</Link>
</div>
);
}}
/>
<Route exact path="/" render={() => <h2>Home</h2>} />
<Route path="/about" render={() => <h2>About</h2>} />
<Route path="/blog" render={() => <h2>Blog</h2>} />
</div>
);
};
Route
の中でLink
を書き出すというものですが、前述したようにchildren
はpath
と一致していない場合でも呼びだされるということを頭に入れるとその挙動がわかると思います。
関数内で受け取るmatch
に値があるときは、Route
のpath
に一致したということです。この例ではpath
が一致しているときは、描画するLink
の横に矢印が追加されるようになっています。
他のLink
でも同様の条件を設けたい場合、上記の方法では1つずつ記述するため冗長になってしまいます。通常はカスタムリンクを作成するのが便利です。
const CustomLink = ({ children, exact, to }) => (
<Route
path={to}
exact={exact}
children={({ match }) => (
<div>
{match ? ">>" : ""}
<Link to={to}>{children}</Link>
</div>
)}
/>
);
const App = () => {
return (
<div>
<CustomLink exact={true} to="/">Home</CustomLink>
<CustomLink to="/about">About</CustomLink>
<Route exact path="/" render={() => <h2>Home</h2>} />
<Route path="/about" render={() => <h2>About</h2>} />
</div>
);
};
CustomLink
は親から受け取った各propsを使い、条件を加えた上でLink
を書き出しています。先の例とやっていることは同じですが、Link
の数が増えてもこの1つのカスタムリンクコンポーネントで処理できます。
尚、CustonLink関数の引数のchildren
は親要素から受け取る子要素を表すpropsで、Route
のchildren
と別物なので注意してください。
NavLink
NavLink
はLink
同様の機能を持っていますが、アクティブなリンクを簡単にスタイリングする機能を持っています。
<NavLink activeClassName="active" to="/" exact>Home</NavLink>
<NavLink activeClassName="active" to="/about">About</NavLink>
<Route exact path="/" render={() => <h2>Home</h2>} />
<Route path="/about" render={() => <h2>About</h2>} />
例えば/
にいるときは、1つ目のNavLink
にactive
というclass名が付与されます。このため、別途CSSでスタイリングを定義する必要があります。
.active {
color: cornflowerblue;
font-weight: bold;
}
尚、exact
をつけると、ロケーションが正確に一致するときのみ適用されます。
処理内でナビゲーション
所謂プログラムチックにロケーションを移動させることもできます。history
オブジェクトを使うだけです。push()
を使うと、historyに新しい履歴を追加することができます。結果、ロケーションが移動します。
class Form extends Component {
onSubmit = () => this.props.history.push("/");
render() {
return (
<form>
<textarea rows="4" cols="40" placeholder="問い合わせ内容" /><br />
<button onClick={this.onSubmit}>Submit</button>
</form>
);
}
}
const App = () => {
return (
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/form">Form</Link></li>
</ul>
<Route exact path="/" render={() => <h2>Home</h2>} />
<Route path="/about" render={() => <h2>About</h2>} />
<Route path="/form" component={Form} />
</div>
);
};
フォームの送信ボタンを押すと、ホームにリダイレクトされます。
Redirect
転送を行うRedirect
もあります。これはメソッドではなくコンポーネントなので、先の例のようにイベントの処理関数に直接記述して移動…いう使い方ではできません。今回は長くなるので触れませんが、Redirectはユーザー認証などでよく利用されます。
to
を指定した先にリダイレクトが行われるため、次の例は/form
へのルーティングを/
に転送し直します。
<Route path="/form" render={() => <Redirect to="/" />} />
サンプルとして先の例を無理矢理Redirectで書き直したものを置いておきます。
withRouter
Link
以外でナビゲーションを行うには、前述したhistory.push
を呼び出すのが一般的です。Route
で直接レンダリングされるコンポーネントは、React Routerのpropsからhistory
にアクセスできます。しかし、直接レンダリングされないコンポーネントではどうでしょう?
アクセスできません!
wtihRouter
は、React RouterのHOC(高階コンポーネント)であり、history
などのReact Routerのpropsを指定コンポーネントにバインドします。使い方はwithRouter
でコンポーネントをラップするだけです。
import { withRouter } from "react-router-dom";
// propsにhistory, location, matchが格納される
const MyButton = withRouter((props) => {
return (
<button onClick={() => props.history.push(props.path)}>
{props.text}
</button>
);
});
<MyButton text="クリック" path="/about" />
のように利用します。このコンポーネントはReact Routerが直接扱うコンポーネントではありませんが、withRouter
によりルーティングのpropsが利用できるようになっています。
余談ですが、history
オブジェクトはhistory.listen()
というメソッドがあります。これはルーティングが変更されるたびにコールバック関数を呼ぶメソッドです。つまりルーティングイベントを監視できます。
Route
と関係のないコンポーネントでもwithRoute
を使うと、このルーティングイベントの変更を取り扱えます。下記はMyComponentがマウントされている限り、ルーティング変更のたびにそのpathname
をコンソール出力します。
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
class MyComponent extends Component {
componentWillMount() {
this.props.history.listen(() => {
console.log(this.props.history.location.pathname);
});
}
render() {
return <p>コンソール画面を確認してください</p>;
}
}
export default withRouter(MyComponent);
解説が前後していますが、withRouter
をステートフルなコンポーネント(クラス)で定義した場合は通常のprops同様、this.props
からReact Routerのpropsにアクセスできます。
Redux
withRouter
はReduxでの接続でも使えます。単にconnect()
を先程のようにwithRouter
でラップするだけです。
withRouter(connect(mapStateToProps)(SomeComponent));
余力があれば、connected-react-routerのようなライブラリも調べてみるといいと思います。