LoginSignup
10
7

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-08-26

基本は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
// 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
//v4
<BrowserRouter>
  <div>
    <Route path='/about' component={About} />
    <Route path='/contact' component={Contact} />
  </div>
</BrowserRouter>

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

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

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

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

v3
<Parent {...routeProps}>
  <Child {...routeProps} />
</Parent>

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

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

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

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

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のサンプル

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、onUpdate、onLeaveでReactのライフサイクルメソッドを再現していましたが、v4では廃止(onUpdateについてはこちら)になりました。v4でRouteはコンポーネントになったため、Reactのライフサイクルメソッドにする必要があります。 

  • onEnterをcomponentDidMountまたはcomponentWillMount
  • onUpdateをcomponentDidUpdateまたはcomponentWillUpdate(またはcomponentWillReceiveProps)
  • onLeaveをcomponentWillUnmount

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

onEnterの書き換えサンプル

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

v3で書いたもの

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

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

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

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

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

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でのサンプル。

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でのサンプル

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

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と格納されるだけでパースしてくれません。

そのため、qhistoryqsquery-stringなどのモジュールを使ってパースする必要があります。

Switch

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

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コンポーネントを使用します。

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

全体

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

v3
// 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
// v3
const THING_PATH = '/thing/:id';

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

v4では、formatPatternの代わりに、path-to-regexpのcompile関数で置き換えます。

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のparse関数で置き換え

Link

to property is required

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

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

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

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>
10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7