基本はMigrating from v2/v3 to v4の翻訳です
モジュール差分
v3のreact-routerとv4のreact-router-domのコンポーネントの比較
モジュール名 | v3 | v4 | 備考 |
---|---|---|---|
BrowserRouter | ○ | ||
HashRouter | ○ | ||
Link | ○ | ○ | v4でtoが必須 |
MemoryRouter | ○ | ||
NavLink | ○ | ||
Prompt | ○ | ||
Redirect | ○ | ○ | |
Route | ○ | ○ | |
Router | ○ | ○ | |
StaticRouter | ○ | ||
Switch | ○ | ||
generatePath | ○ | ||
matchPath | ○ | ||
withRouter | ○ | ○ | |
IndexLink | ○ | ||
IndexRedirect | ○ | ||
IndexRoute | ○ | ||
createRoutes | ○ | ||
RouterContext | ○ | ||
locationShape | ○ | ||
routerShape | ○ | ||
match | ○ | ||
useRouterHistory | ○ | ||
formatPattern | ○ | ||
applyRouterMiddleware | ○ | ||
browserHistory | ○ | ||
hashHistory | ○ | ||
createMemoryHistory | ○ |
空欄はそのバージョンで存在しないモジュール
v4でv3の大半は廃止になっています。
react-router v3からv4へのマイグレーション
v3とv4では破壊的変更があり、簡単には移行ができません。
Router
v3までのreact-routerはアプリケーションに1つのRouterコンポーネントに複数のRouteコンポーネントをネストさせてrouterの設定をしてました。
また、Routerの小要素にはreact-router関連のコンポーネントしか置けません。
// v3
import routes from './routes'
<Router history={browserHistory} routes={routes} />
// or
<Router history={browserHistory}>
<Route path='/' component={App}>
// ...
</Route>
</Router>
v4では、v3とは異なり、Routerコンポーネントの小要素には、好きなコンポーネントを配置できるようになりました。
また、v4で大きく3つのRouterコンポーネンネントが提供されるようになりました(react-router-comパッケージ)。BrowserRouter、HashRouter、MemoryRouterの3つです。(react-router-reduxには他のRouterもあります。)
BrowserRouterはHistoryAPIを使用してUIをURLと同期させるRouterを作成、HashRouterは履歴をwindow.location.hashを使ってUIをURLと同期させるRouterを作成、MemoryRouterは履歴をメモリ管理するRouterを作成します(アドレスバーを読み書きせず、React NativeやTestで役立つRouter)。
v3で<Router history={browserHistory}>
と書いていましたが、v4でbrowserHistory
が廃止となります。v4では、単純な実装な場合はRouterからBrowserRouterに書き換えるだけになるかと思います。BrowserRouterは、historyを生成してRouterコンポーネントをレンダリングするコンポーネントになります。
//v4
<BrowserRouter>
<div>
<Route path='/about' component={About} />
<Route path='/contact' component={Contact} />
</div>
</BrowserRouter>
注意点としてRouterは、一つしか小要素を持つことができないので、RouterコンポーネントにRouteを使うときはdivタグなどでRouteコンポーネントを加工必要があります。
// yes
<BrowserRouter>
<div>
<Route path='/about' component={About} />
<Route path='/contact' component={Contact} />
</div>
</BrowserRouter>
// no
<BrowserRouter>
<Route path='/about' component={About} />
<Route path='/contact' component={Contact} />
</BrowserRouter>
BrowserRouterコンポーネントのrender
では、react-routerのRouterコンポーネントをレンダリングしてchildrenにRouteコンポーネントを渡しているためです。v3では、Routerにはhistoryは渡す必要がありましたが、v4のBrowserRouterでは、コンポーネント内部でhistoryを生成しているので、渡す必要ななくなります。
class BrowserRouter extends React.Component {
...
history = createHistory(this.props);
...
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
export default BrowserRouter;
Route
v3のRouteは、コンポーネントではありませんでしたが、v4でコンポーネントになりました。v3のRouteは、route構成専用でレンダリングしようとするとinvariantでレンダリングするべきではない旨が出ます。
v4 Routeは実際のコンポーネントなので、Routeコンポーネントをレンダリングすると、Routeで表示したいコンポーネントがレンダリングされます。 Routeのパスが現在のパスと一致する場合、コンポーネント、render、またはその子コンポーネントをレンダリングします。 Routeのパスが一致しない場合、nullがレンダリング(何もレンダリングされない)されます。
他にも大きな変更点として、v3のRouteは排他的で、urlにマッチされた最初のRouteだけレンダリングしていましたが、v4で非排他的になり、urlにマッチされるRouteをすべてレンダリングするようになりました。
Nesting Routes
v3では、Routeは親Routeの子として渡すことでネストされました。
<Route path='parent' component={Parent}>
<Route path='child' component={Child} />
<Route path='other' component={Other} />
</Route>
上記のコードだと、Parentコンポーネント内で、props.children
を記述しておけば、parent/childのパスにアクセスしてもParentコンポーネントがレンダリングされました。Parentコンポーネントが、子コンポーネントの共通分(ヘッダやフッターなど)をレンダリングするといったことができました。
(Routeではなく、それぞれのコンポーネントで表すと以下のような意味合いになります。Parentコンポーネントのprops.childrenにChildコンポーネントが入る。)
<Parent {...routeProps}>
<Child {...routeProps} />
</Parent>
v4では、v3のような入れ子(コンポーネントの小要素)はなくなりました。そのため、共通となるコンポーネントParentがChildコンポーネントのRouteを記述する形になります。
<Route path='parent' component={Parent} />
const Parent = () => (
<div>
<Route path='child' component={Child} />
<Route path='other' component={Other} />
</div>
)
以下はv3で書いたNesting Routesのサンプル
import React from "react";
import ReactDOM from "react-dom";
import { Router, Route, Link, browserHistory } from "react-router";
import "./styles.css";
class Parent extends React.Component {
render() {
return (
<div>
<h1>Parent Component</h1>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="child">Child</Link>
</li>
</ul>
<div>{this.props.children}</div>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<h1>Child Component</h1>
<div>Hello React</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Parent}>
<Route path="child" component={Child} />
</Route>
</Router>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
以下はv4で書いたNesting Routesのサンプル
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Link, Switch } from "react-router-dom";
import "./styles.css";
class Parent extends React.Component {
render() {
return (
<div>
<h1>Parent Component</h1>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="child">Child</Link>
</li>
</ul>
<div>
<Switch>
<Route path="/child" component={Child} />
</Switch>
</div>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<h1>Child Component</h1>
<div>Hello React</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<BrowserRouter>
<Route path="/" component={Parent} />
</BrowserRouter>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
内容はv3,v4で全く同じです。
v3のNesting Routesの書き方ができなくなるのでだいぶコードが変わります。
on* properties
v3のRouteでは、onEnter、onUpdate、onLeaveでReactのライフサイクルメソッドを再現していましたが、v4では廃止(onUpdateについてはこちら)になりました。v4でRouteはコンポーネントになったため、Reactのライフサイクルメソッドにする必要があります。
- onEnterをcomponentDidMountまたはcomponentWillMount
- onUpdateをcomponentDidUpdateまたはcomponentWillUpdate(またはcomponentWillReceiveProps)
- onLeaveをcomponentWillUnmount
に変更する必要があります。
onEnterの書き換えサンプル
onEnter(componentWillMount)でalertを表示するだけのサンプルを以下で紹介します.
v3で書いたもの
import React from "react";
import ReactDOM from "react-dom";
import { Router, Route, browserHistory } from "react-router";
import "./styles.css";
class Home extends React.Component {
render() {
return (
<div>
<h1>Home Component</h1>
<div>Hello!</div>
</div>
);
}
}
const enterHomee = () => {
alert("Enter Home");
};
class App extends React.Component {
render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Home} onEnter={enterHomee} />
</Router>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
v4で書いたもの
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
import "./styles.css";
class Home extends React.Component {
componentWillMount() {
enterHomee();
}
render() {
return (
<div>
<h1>Home Component</h1>
<div>Hello!</div>
</div>
);
}
}
const enterHomee = () => {
alert("Enter Home");
};
class App extends React.Component {
render() {
return (
<BrowserRouter>
<Route path="/" component={Home} />
</BrowserRouter>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
routeでやっていた内容をrouteで描画するコンポーネントに処理を移譲する感じになります。
onUpdateの書き換えサンプル
onUpdate(componentDidUpdate)でページトップにスクロールするサンプルを以下で紹介します.
v3で書いたもの
import React from "react";
import ReactDOM from "react-dom";
import { Router, Route, Link, browserHistory } from "react-router";
import "./styles.css";
class Parent extends React.Component {
render() {
return (
<div>
<h1>Parent Component</h1>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="child">Child</Link>
</li>
</ul>
<div>{this.props.children}</div>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<h1>Child Component</h1>
<div>Hello React</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<Router
history={browserHistory}
onUpdate={() => {
window.scrollTo(0, 0);
}}
>
<Route path="/" component={Parent}>
<Route path="child" component={Child} />
</Route>
</Router>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
v4で書いたもの
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Link, Switch } from "react-router-dom";
import "./styles.css";
import ScrollToTop from "./ScrollToTop";
class Parent extends React.Component {
render() {
return (
<div>
<h1>Parent Component</h1>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="child">Child</Link>
</li>
</ul>
<div>
<Switch>
<Route path="/child" component={Child} />
</Switch>
</div>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<h1>Child Component</h1>
<div>Hello React</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<BrowserRouter>
<ScrollToTop>
<Route path="/" component={Parent} />
</ScrollToTop>
</BrowserRouter>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
import React from "react";
import { withRouter } from "react-router-dom";
class ScrollToTop extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0);
}
}
render() {
return this.props.children;
}
}
export default withRouter(ScrollToTop);
onLeaveの書き換えサンプル
onLeave(componentWillUnmount)でalertを表示するサンプルを以下で紹介します.
v3で書いたもの
import React from "react";
import ReactDOM from "react-dom";
import { Router, Route, Link, browserHistory } from "react-router";
import "./styles.css";
class Parent extends React.Component {
render() {
return (
<div>
<h1>Parent Component</h1>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="child">Child</Link>
</li>
</ul>
<div>{this.props.children}</div>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<h1>Child Component</h1>
<div>Hello React</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Parent}>
<Route
path="child"
component={Child}
onLeave={() => alert("leave")}
/>
</Route>
</Router>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
v4で書いたもの
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Link, Switch } from "react-router-dom";
import "./styles.css";
class Parent extends React.Component {
render() {
return (
<div>
<h1>Parent Component</h1>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="child">Child</Link>
</li>
</ul>
<div>
<Switch>
<Route path="/child" component={Child} />
</Switch>
</div>
</div>
);
}
}
class Child extends React.Component {
componentWillUnmount() {
alert("leave");
}
render() {
return (
<div>
<h1>Child Component</h1>
<div>Hello React</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<BrowserRouter>
<Route path="/" component={Parent} />
</BrowserRouter>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Optional Parameters
オプションパラメータの渡し方は、v3、v4でそこまでかわりません。
v3では、path="/entity/:entityId(/:parentId)"
だったのに対してv4では、path="/entity/:entityId/:parentId?"
となります。
(/:parentId)
が、/:parentId?
に変わるだけです。カッコがなくなり代わりに?になるだけです。
v3でのサンプル。
import React from "react";
import ReactDOM from "react-dom";
import { Router, Route, browserHistory } from "react-router";
import "./styles.css";
class Home extends React.Component {
render() {
const { params } = this.props;
return (
<div>
<h1>Home Component</h1>
<div>Hello!</div>
{params.pathParam}
</div>
);
}
}
class App extends React.Component {
render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Home} />
<Route path="/home(/:pathParam)" component={Home} />
</Router>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
v4でのサンプル
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";
import "./styles.css";
class Home extends React.Component {
render() {
const { params } = this.props.match;
return (
<div>
<h1>Home Component</h1>
<div>Hello!</div>
{params.pathParam}
</div>
);
}
}
class App extends React.Component {
render() {
return (
<BrowserRouter>
<div>
<Route exact path="/" component={Home} />
<Route path="/home/:pathParam?" component={Home} />
</div>
</BrowserRouter>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Query Strings
v3では、query stringをパースしてlocation.queryにqueryパラメータを格納していました。
import React from "react";
import ReactDOM from "react-dom";
import { Router, Route, browserHistory } from "react-router";
import "./styles.css";
class Home extends React.Component {
render() {
const { query } = this.props.location;
console.log(query);
return (
<div>
<h1>Home Component</h1>
<div>Hello!</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Home} />
</Router>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
v4では、v3のようにParseしてパラメータに格納してくれません。
?test=123というquery stringをv4で同じようなコードで送っても、query stringはlocation.search
に?test=123
と格納されるだけでパースしてくれません。
そのため、qhistory、qs、query-stringなどのモジュールを使ってパースする必要があります。
Switch
v4では、IndexRouterが廃止になり、Routeは非排他的になりました。
v4で以下のようなコードを書いた場合、urlが/item/detail"の場合、
/item/:idと
/item/detail``がレンダリングされてしまいます。
<BrowserRouter >
<div>
<Route path="/item" component={ItemList} />
<Route path="/item/:id" component={ItemInfo} />
<Route path="/item/detail" component={ItemDetail} />
</div>
</Router>
urlにマッチしたRouteだけをレンダリングしたい場合は、Switch
コンポーネントを使用します。
<BrowserRouter >
<Switch>
<Route path="/item" component={ItemList} />
<Route path="/item/:id" component={ItemInfo} />
<Route path="/item/detail" component={ItemDetail} />
</Switch>
</Router>
全体
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import "./styles.css";
class ItemList extends React.Component {
render() {
return (
<div>
<h1>Item List</h1>
<div>Hello!</div>
</div>
);
}
}
class ItemInfo extends React.Component {
render() {
return (
<div>
<h1>Item Info</h1>
<div>Hello!</div>
</div>
);
}
}
class ItemDetail extends React.Component {
render() {
return (
<div>
<h1>Item Detail</h1>
<div>Hello!</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/item" component={ItemList} />
<Route exact path="/item/:id" component={ItemInfo} />
<Route exact path="/item/detail" component={ItemDetail} />
</Switch>
</BrowserRouter>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Redirect
v3では、あるパスから別のパスにリダイレクトしたいときは、IndexRedirect
を利用していたと思いますがIndexRedirectはv4で、廃止になっています。
v4では、Redirect
で同じ動作をすることができます。
v3にもRedirect
は存在していますが、v4とは少し動作が異なります。
v3の場合、パスにパラメータがあったがあったらリダイレクト先にもパラメータが自動的に渡されていました。
以下のサンプルの場合、/
に?source=google
をつけてアクセスすると/welcome?source=google
にリダイレクトされます。
// v3
<Redirect from="/" to="/welcome" />
// /?source=google → /welcome?source=google
一方v4では、リダイレクト先にもパラメータが渡されなくなりました。そのため、再度パラメータを詰めて上げる必要があります。
// v4
<Redirect from="/" to="/welcome" />
// /?source=google → /welcome
<Redirect from="/" to={{ ...location, pathname: "/welcome" }} />
// /?source=google → /welcome?source=google
PatternUtils
v4では、PatternUtils自体が廃止になります。
matchPattern(pattern, pathname)
v4では、パスマッチングにpath-to-regexpで強化されたmatchPathを提供されるようになりました。
formatPattern(pattern, params)
v3では、PatternUtils.formatPatternを使用して、パスパターンとnameパラメータを含むオブジェクトからパスを生成することができました。
// v3
const THING_PATH = '/thing/:id';
<Link to={PatternUtils.formatPattern(THING_PATH, {id: 1})}>A thing</Link>
v4では、formatPatternの代わりに、path-to-regexpのcompile関数で置き換えます。
// v4
const THING_PATH = '/thing/:id';
const thingPath = pathToRegexp.compile(THING_PATH);
<Link to={thingPath({id: 1})}>A thing</Link>
getParamNames
同等の機能はpath-to-regexpのparse関数で置き換え
Link
to property is required
v3では、toの省略かnullセットができましたが、v4でLinkコンポーネントのtoプロパティが必須になりました。
// v3
<Link to={disabled ? null : `/item/${id}`} className="item">
// item content
</Link>
v4でhref(to)が無いリンクを作成するには、Linkコンポーネントのラッパーコンポーネントを作成する必要があります。
// v4
import { Link } from 'react-router-dom'
const LinkWrapper = (props) => {
const Component = props.to ? Link : 'a'
return (
<Component {...props}>
{ props.children }
</Component>
)
}
<LinkWrapper to={disabled ? null : `/item/${id}`} className="item">
// item content
</LinkWrapper>