react-router v3からv4へのマイグレーション

基本は[Migrating from v2/v3 to v4](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md#link)の翻訳です

# モジュール差分
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関連のコンポーネントしか置けません。

```react:v3
// v3
import routes from './routes'
<Router history={browserHistory} routes={routes} />
// or
<Router history={browserHistory}>
  <Route path='/' component={App}>
    // ...
  </Route>
</Router>
```

v4では、v3とは異なり、Routerコンポーネントの小要素には、好きなコンポーネントを配置できるようになりました。
また、v4で大きく３つの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コンポーネントをレンダリングするコンポーネントになります。

```jsx:v4
//v4
<BrowserRouter>
  <div>
    <Route path='/about' component={About} />
    <Route path='/contact' component={Contact} />
  </div>
</BrowserRouter>
```

注意点としてRouterは、一つしか小要素を持つことができないので、RouterコンポーネントにRouteを使うときはdivタグなどでRouteコンポーネントを加工必要があります。

```jsx:v4
// 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を生成しているので、渡す必要ななくなります。

```js:BrowserRouter.js
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の子として渡すことでネストされました。

```jsx:v3
<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コンポーネントが入る。)

```jsx:v3
<Parent {...routeProps}>
  <Child {...routeProps} />
</Parent>
```

v4では、v3のような入れ子（コンポーネントの小要素）はなくなりました。そのため、共通となるコンポーネントParentがChildコンポーネントのRouteを記述する形になります。

```jsx:v4
<Route path='parent' component={Parent} />

const Parent = () => (
  <div>
    <Route path='child' component={Child} />
    <Route path='other' component={Other} />
  </div>
)
```

以下はv3で書いたNesting Routesのサンプル

```js:index.js
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のサンプル

```js:index.js
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](https://github.com/ReactTraining/react-router/blob/afa0560326eebc4ac426bb206ffd2b07f5448964/docs/Glossary.md#enterhook)、onUpdate、[onLeave](https://github.com/ReactTraining/react-router/blob/afa0560326eebc4ac426bb206ffd2b07f5448964/docs/Glossary.md#leavehook)でReactのライフサイクルメソッドを再現していましたが、[v4では廃止](https://github.com/ReactTraining/react-router/issues/3854)(onUpdateについては[こちら](https://github.com/ReactTraining/react-router/issues/4278))になりました。v4でRouteはコンポーネントになったため、Reactのライフサイクルメソッドにする必要があります。　

 + onEnterをcomponentDidMountまたはcomponentWillMount
 + onUpdateをcomponentDidUpdateまたはcomponentWillUpdate（またはcomponentWillReceiveProps）
 + onLeaveをcomponentWillUnmount

に変更する必要があります。

### onEnterの書き換えサンプル

onEnter(componentWillMount)でalertを表示するだけのサンプルを以下で紹介します.

v3で書いたもの


```js:index.js
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で書いたもの

```js:index.js
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で書いたもの


```js:index.js
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で書いたもの


```js:index.js
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);
```

```js:ScrollToTop.js
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で書いたもの

```js:index.js

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で書いたもの

```js:index.js
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でのサンプル。


```js:index.js
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でのサンプル

```js:index.js
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パラメータを格納していました。

```js:index.js
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](https://www.npmjs.com/package/qhistory)、[qs](https://www.npmjs.com/package/qs)、[query-string](https://www.npmjs.com/package/query-string)などのモジュールを使ってパースする必要があります。

## Switch
v4では、IndexRouterが廃止になり、Routeは非排他的になりました。
v4で以下のようなコードを書いた場合、urlが``/item/detail"の場合、``/item/:id``と``/item/detail``がレンダリングされてしまいます。


```jsx:sample
<BrowserRouter >
  <div>
    <Route path="/item" component={ItemList} />
    <Route path="/item/:id" component={ItemInfo} />
    <Route path="/item/detail" component={ItemDetail} />
  </div>
</Router>
```

urlにマッチしたRouteだけをレンダリングしたい場合は、``Switch``コンポーネントを使用します。


```jsx:sample
<BrowserRouter >
  <Switch>
    <Route path="/item" component={ItemList} />
    <Route path="/item/:id" component={ItemInfo} />
    <Route path="/item/detail" component={ItemDetail} />
  </Switch>
</Router>
```
全体

```js:index.js
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``にリダイレクトされます。

```jsx:v3
// v3

<Redirect from="/" to="/welcome" />
// /?source=google → /welcome?source=google
```

一方v4では、リダイレクト先にもパラメータが渡されなくなりました。そのため、再度パラメータを詰めて上げる必要があります。

```jsx:
// 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](https://www.npmjs.com/package/path-to-regexp)で強化されたmatchPathを提供されるようになりました。

### formatPattern(pattern, params)
v3では、PatternUtils.formatPatternを使用して、パスパターンとnameパラメータを含むオブジェクトからパスを生成することができました。

```js:v3
// v3
const THING_PATH = '/thing/:id';

<Link to={PatternUtils.formatPattern(THING_PATH, {id: 1})}>A thing</Link>
```

v4では、formatPatternの代わりに、[path-to-regexp](https://www.npmjs.com/package/path-to-regexp)のcompile関数で置き換えます。

```js:v4
// v4
const THING_PATH = '/thing/:id';

const thingPath = pathToRegexp.compile(THING_PATH);

<Link to={thingPath({id: 1})}>A thing</Link>
```



### getParamNames
同等の機能は[path-to-regexp](https://www.npmjs.com/package/path-to-regexp)のparse関数で置き換え

## Link

### to property is required
v3では、toの省略かnullセットができましたが、v4でLinkコンポーネントのtoプロパティが必須になりました。

```jsx:v3
// v3
<Link to={disabled ? null : `/item/${id}`} className="item">
  // item content
</Link>
```

v4でhref(to)が無いリンクを作成するには、Linkコンポーネントのラッパーコンポーネントを作成する必要があります。

```js:LinkWrapper
// 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>
```