あらすじ
react-router@v4 にしたら色々な変更が入っていて全く動かなくなったので react-router の機能を確認しました。
追記: 2017-03-23 16:30
withRouter について追記しました。
言及すること
<Route>の挙動が v3 と異なる<Switch>と<Route exact ...>の挙動を理解して対処しましょうbrowserHistory.pushから `this.props.history.push へ変わった<NavLink>が追加されました- Nested Routes が廃止された?
- withRouter
準備
create-react-app practice-react-router4 && cd $_ npm install --save react-router-dom
<Route>
最初に src/App.js を以下のように修正します。
import React from 'react' import { BrowserRouter as Router, Switch, Route, Redirect, Link, NavLink, withRouter } from 'react-router-dom' const Home = () => (<h2>Home</h2>) const About = () => (<h2>About</h2>) const User = () => (<h2>User</h2>) const App = () => ( <Router> <div style={{padding: "60px"}}> <Route path="/" component={Home} /> <Route path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/> </div> </Router> ) export default App
この状態で http://localhost:3000/, http://localhost:3000/okamuuu, `http://localhost:3000/about にアクセスするとそれぞれ以下の画面が表示されます。
open http://localhost:3000/
<div data-reactroot="" style="padding: 60px;"> <h2>Home</h2> <h2>NoMatch</h2> </div>
open http://localhost:3000/okamuuu
<div data-reactroot="" style="padding: 60px;"> <h2>Home</h2> <h2>User: okamuuu</h2> <h2>NoMatch</h2> </div>
open http://localhost:3000/about
<div data-reactroot="" style="padding: 60px;"> <h2>Home</h2> <h2>About</h2> <h2>User: okamuuu</h2> <h2>NoMatch</h2> </div>
このような <Route> の使い方をすると、version4 では URL にマッチする component を全て含んでしまいます。
<Swtich>
先ほどの <Route>s の直上に <Switch> を加えます。
const App = () => ( <Router> <div style={{padding: "60px"}}> + <Switch> <Route path="/" component={Home} /> <Route path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/> + </Switch> </div> </Router> )
この状態で http://localhost:3000/, http://localhost:3000/okamuuu, `http://localhost:3000/about にアクセスすると全て以下の画面が表示されます。
<div data-reactroot="" style="padding: 60px;"> <h2>Home</h2> </div>
これは最初に Match する要素が発見され次第、その要素だけを返すからです。いまのままでは全ての location に対して <Home> を返してしまうので以下のように正確に Match した場合だけ判定するように <Route> に exact=true を追加します。
open http://localhost:3000/
<div data-reactroot="" style="padding: 60px;"> <h2>Home</h2> </div>
open http://localhost:3000/okamuuu
<div data-reactroot="" style="padding: 60px;"> <h2>User: okamuuu</h2> </div>
open http://localhost:3000/about
<div data-reactroot="" style="padding: 60px;"> <h2>About</h2> </div>
browserHistory.push
以下のように props で受け取る形式に変わっています。
-const User = (props) => (<h2>User: {props.match.params.user}</h2>) +const User = (props) => ( + <div> + <h2>User: {props.match.params.user}</h2> + <button onClick={() => props.history.push("/")}>Back to Home</button> + </div> +)
<NavLink>
src/App.js を以下のように修正します。
const App = () => (
<Router>
<div style={{padding: "60px"}}>
+ <ul>
+ <li><NavLink to="/">Home</NavLink></li>
+ <li><NavLink to="/about">About</NavLink></li>
+ <li><NavLink to="/okamuuu">User</NavLink></li>
+ </ul>
<Switch>
<Route exact path="/" component={Home} />
Match する場合は className に active が追加されます。
open http://localhost:3000/
<ul>
<li><a class="active " href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/okamuuu">User</a></li>
</ul>
open http://localhost:3000/okamuuu
<ul>
<li><a class="active " href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a class="active " href="/okamuuu">User</a></li>
</ul>
open http://localhost:3000/about
<ul>
<li><a class="active " href="/">Home</a></li>
<li><a class="active href="/about">About</a></li>
<li><a href="/okamuuu">User</a></li>
</ul>
便利な機能ですがこのままだと / も Active になってしまいます。<Route> の時のように extract を指定します。
- <li><NavLink to="/">Home</NavLink></li> + <li><NavLink exact to="/">Home</NavLink></li>
また CSS in JS したい場合は className を各自で好きなように指定したい場合は以下のようにします。
CSS in JS したい
<NavLink
to="/about"
activeStyle={{
fontWeight: 'bold',
color: 'red'
}}
>About</NavLink>
className を好きにしたい
<NavLink to="/about" activeClassName="selected" >About</NavLink>
個人的な意見ですが NavLink を使うような components はロジックと分離したいのであまり使わないかもしれないのですが一応使い方は覚えておいたほうがいいと思います。
ロジックの分離: About がクリックされたら親コンポーネントに「今 About が押されたよ。あとよろ」と実装して web components として再利用しやすい状態にしたい。
Nested Routes 廃止?
v3 では共通の components を使用したい場合は {this.props.children} と書いて Nested Routes していたのですが、v4 からはそうではなくなっているようです。
withRouter
Home へ戻るボタンを追加したいのですが、ボタン自体に hisotry オブジェクトを渡さない場合、呼び出し元で hisotry.push 関数を実行したいのですが react-router@v4 から browserHistory が import できなくなっています。
代わりに withRouter を使います。
edit src/App.js
const HomeButton = ({onClick}) => ( <button onClick={onClick}>push Home</button> ) const Routes = withRouter((props) => { const {push} = props.history return ( <div style={{padding: "60px"}}> <ul> <li><NavLink exact to="/">Home</NavLink></li> <li><NavLink to="/about">About</NavLink></li> <li><NavLink to="/okamuuu">User</NavLink></li> </ul> <Switch> <Route exact path="/" component={Home} /> <Route exact path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/> </Switch> <HomeButton onClick={() => push("/")} /> </div> ) }) const App = () => ( <Router> <Routes /> </Router> )
まとめ
というわけで v3 から v4 に移行する場合、小さなアプリはいいと思いますがそうでない場合はちょっと面倒くさいかもしれません。こちらからは以上です。