* 当サイトはアフィリエイト広告を利用しています。

Webプログラミング

React Router v6がリリースされたので端的にまとめます

React Routerの新たなバージョンがリリースされ、v6となりました。

今までのバージョンの「いいとこ取りをしたAPIを作った」とのことです。

今回の記事ではv5からの変更点などを含め、要点をまとめていきます。

v5からいくつかの破壊的な変更がありますので、アップグレードの際は注意して下さい。

こんな方におすすめ

  • React Router v6について要点だけ知りたい方
  • React Router v5からの移行に関する情報が欲しい方
  • 公式の移行ガイドが長すぎるという方

React Router v6の情報

React Router v6の改善された点 & 特徴

React Router v6 の改善された点 & 特徴は主に以下のようなものが挙げられます。

  • 階層構造のパスが直感的に設定出来るようになった
  • Routeが設定の順番に左右されなくなった(現在のURLに最適なRouteを自動的に選択)
  • コード分割が容易になり、遅延ロードも書きやすくなった
  • 今までのReact Routerよりもコードがコンパクトになりやすい
  • バンドルサイズが減少する

ものすごく大きく変わった、ということではありません。あくまで以前のバージョンから改善されたという感じです。

ソースコードを見るのが手っ取り早いと思いますので、React Router v6で書かれたサンプルを見てみます。

  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route index element={<Home />} />
        <Route path="teams" element={<Teams />}>
          <Route path=":teamId" element={<Team />} />
          <Route path="new" element={<NewTeamForm />} />
          <Route index element={<LeagueStandings />} />
        </Route>
      </Route>
    </Routes>
  </BrowserRouter>

上のようにソースコードの一箇所でまとめて階層構造のRoute設定が出来る上、パスの設定が相対パスで指定できるようになったことで今までよりも直感的になりました。

またReact Router v5までの場合、Routeの書く順番を考慮しなければならず、場合によってはexactを指定する必要がありました。

React Router v6ではURLに最適なRouteを自動で見つけてくれます。つまり、

<Route path="teams" element={<Teams />}>
    <Route path=":teamId" element={<Team />} />
    <Route path="new" element={<NewTeamForm />} />

今までは上から順にURLパターンにマッチするか確認されていたため、newのほうのRouteを:teamIdのRouteの上に書く必要がありました。

v6では最適なRouteを自動で見つけてくれるため、この例でもURLが/teams/newであれば<NewTeamForm />をレンダーしてくれます。

他にも遅延ロードがしやすいようになっているのもv6の特徴です。例↓

    <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route
            path="dashboard/*"
            element={
              <React.Suspense fallback={<>...</>}>
                <Dashboard />
              </React.Suspense>
            }
          />
         ......

補足情報

v6では前までのバージョンにあった”曖昧さ”も解消されています。

v5では<Link to='......'>の挙動が曖昧でした。例えば「現在のURLの末尾にスラッシュがついているかどうか」で挙動が変わったりしていましたが、このような曖昧さもv6では解消されています。

以上がざっくりとしたReact Router v6の改善された点 & 特徴です。今までよりも使いやすくなっているという印象です。

以降のセクションでは移行の情報について書いていきます。

React Router v5を使っている人はどうするの?

要するに...

  • v6は今までのReact RouterとReach Routerの後継であり、可能ならアップグレードした方がいい
  • v5との後方互換も開発中なので、急ぐ必要はない
  • v5も当面の間はアップデートがされる

とのことです。

公式ドキュメントには以下のように書いてあります。

We recommend waiting for the backwards compatibility package to be released before upgrading apps that have more than a few routes.

Upgrading from v5 > Backwards Compatibility Package

↓↓↓

複数のRouteを設定してあるアプリのアップグレードは後方互換のパッケージがリリースされるまで待つことを推奨します。

とはいえ小さなプロジェクトであればすぐにアップグレード出来ますので、以降はアップグレードについての情報をまとめていきます。

アップグレード

アップグレードの手順

  1. Reactのバージョンをv16.8以上へアップグレードする
  2. (React Router v5.1未満の場合は) React Router v5.1へアップグレード
  3. React Router をv6へアップグレード

現在使っているReact Routerのバージョンがv5.1未満の場合は、まず一旦React Router v5.1へアップグレードした方がいいようです。

v5.1へのアップグレードは以下の項目を参考に:

v6へのアップグレード (主なところだけ抽出)

記事を修正しました

パッケージのアップグレード

まずはパッケージのアップグレードをします。

依存パッケージだったhistoryパッケージの扱いが変わったので、念のため一度削除してからインストールした方が間違いないと思います。

yarnの例 ↓↓

$ yarn remove react-router-dom

次に確認のためhistoryを手動でアンインストール

$ yarn remove history

historyパッケージが削除済みならエラーが出ますが、それでOKです。

最後にバージョン指定でreact-router-domとhistoryをインストールします。

$ yarn add history@5 react-router-dom@6

これでパッケージのアップグレードは完了です。

ソースコードの修正

ここからはソースコード上の修正の要点だけを抽出します。

分かりやすくするため「書き換えだけ」、「簡単な移行」、「ちょっと面倒な移行」の順でグループ分けしています。

詳細の解説は公式ドキュメントを参考にして下さい。(長いですが)

書き換えだけ

書き換えだけで済む移行は以下のようなものが挙げられます。

  • <Switch><Routes>へ書き換え
  • react-router-configパッケージを使っていた人はuseRoutesとして実装されたので、そちらを使う
  • <NavLink exact><NavLink end>へプロパティ名を書き換え

これらはほんの数分で出来ると思います。

簡単な移行

簡単な移行は以下のようなものがあります。少し書き換えが必要です。

  • useHistoryではなくuseNavigateを使う (詳細は↓で)
  • NavLinkのactiveClassName, activeStyleプロパティが削除された(詳細は↓で)
useHistoryからuseNavigateへの移行について

今までナビゲーション(ページ遷移)にはhistory.pushhistory.replaceもしくは <Redirect to={ ... を使っていましたが、変更が必要です。

命令型の場合( history.pushhistory.replaceで書いていたコード)は、以下のようにnavigateへと変更します。

import { useNavigate } from "react-router-dom";
function App() {
  let navigate = useNavigate();
  function handleClick() {
    navigate("/home");
  }
  return (
    <div>
      <button onClick={handleClick}>go home</button>
    </div>
  );
}

宣言型の場合( <Redirect to={ ... で書いていたコード)は、以下のように変更します。

import { Navigate } from "react-router-dom";
function App() {
  return <Navigate to="/home" replace state={state} />;
}

変更内容は単純ですが、地味に書き換え箇所が多いかもしれません。

NavLinkのactiveClassName, activeStyleプロパティが削除されました。

その代わりactive状態のスタイルを指定したい場合は以下のように書きます。

<NavLink style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })} ...

<NavLink className={({ isActive }) => "nav-link" + (isActive ? " activated" : "")} ...

これも場合によっては変更箇所が多いかもしれません。

ちょっと面倒な移行

ここからがちょっと面倒な移行で、それぞれ複数の書き換えが必要です。

  • Routeの書き方が変わった
  • Linkの書き方が変わった

どちらも解説していきます。

Routeの書き方が変わった

まずは<Route>のプロパティについてです。

v5ではRouteでレンダーするコンポーネントは以下のように指定していました。

<Route exact path="/">
    <Home />
</Route>

これをv6ではelementプロパティとして指定します。

<Route path="/" element={<Home />} />

ちなみに以下のようにpropsも渡せます。

<Route path="/" element={<Home animate={true} />} />

次にRouteのpathプロパティですが、正規表現は使えなくなりパラメータ(例えば/users/:id)・ワイルドカード(例えば/users/*)だけになりました。

v6でのpathパターン↓

/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*

このパターンを参考にpathの書き換えを行って下さい。

最後にRouteの設定方法ですが、大きく分けて2パターンに分けられます。

  • Routeをまとめて設定する場合
  • Routeを分割して設定する場合
Routeを分割して設定する場合

Routeを分割する場合は各コンポーネントでRouteを<Routes>で囲います。

import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users/*" element={<Users />} />
      </Routes>
    </BrowserRouter>
  );
}
function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>
      <Routes>
        <Route path=":id" element={<UserProfile />} />
        <Route path="me" element={<OwnUserProfile />} />
      </Routes>
    </div>
  );
}

この例のように分割してRoutesを指定することが出来ます。pathが親ルートに対する相対パスになっていることに注意して下さい。

Routeをまとめて設定する場合

Routeは入れ子にして設定し、pathは親ルートに対する相対パスで指定します。

例 ↓

import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users" element={<Users />}>
          <Route path="me" element={<OwnUserProfile />} />
          <Route path=":id" element={<UserProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

また子コンポーネントをレンダリングするには<Outlet />を使います。

function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>
      <Outlet />
    </div>
  );
}

以上です。v5とv6のソースコードの違いがわかるように下にまとめておきます。参考にして下さい。

Route変更点まとめ

React Router v5の場合は以下のように書いていました。

import { BrowserRouter, Switch, Route, Link, useRouteMatch } from "react-router-dom";
function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/">
          <Home />
        </Route>
        <Route path="/users">
          <Users />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}
function Users() {
  // v5では入れ子になったroutesは子コンポーネントでレンダーされる
  // 入れ子になった子コンポーネント側でSwitchで定義する必要がある.
  // matchを使う必要もあった。 
  let match = useRouteMatch();
  return (
    <div>
      <nav>
        <Link to={`${match.url}/me`}>My Profile</Link>
      </nav>
      <Switch>
        <Route path={`${match.path}/me`}>
          <OwnUserProfile />
        </Route>
        <Route path={`${match.path}/:id`}>
          <UserProfile />
        </Route>
      </Switch>
    </div>
  );
}

これがv6で以下のように書き方が変わりました。

import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users/*" element={<Users />} />
      </Routes>
    </BrowserRouter>
  );
}
function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>
      <Routes>
        <Route path=":id" element={<UserProfile />} />
        <Route path="me" element={<OwnUserProfile />} />
      </Routes>
    </div>
  );
}
// もしくは一箇所で入れ子にしてRouteを指定できます。
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="users" element={<Users />}>
          <Route path="me" element={<OwnUserProfile />} />
          <Route path=":id" element={<UserProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
// Routeのレンダーのために<Outlet />が必要
function Users() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>
      <Outlet />
    </div>
  );
}

ソースコードがシンプルになっていることが確認できると思います。

大きく分けて2つ変わっています。

toプロパティで相対パスを指定することができるようになった

<Link>コンポーネントのtoプロパティの書き方も変更になり、Routeのpathと同じように親ルートに対する相対パスを指定できるようになりました。

例えば以下のようなRoutesの場合を考えます。

<Route path="app">
  <Route path="dashboard">
    <Route path="stats" />
  </Route>
</Route>

現在のURLが/app/dashboardの場合、<Link to={...から生成されるリンクは以下のようになります。

<Link to="stats">            => <a href="/app/dashboard/stats">
<Link to="../stats">          => <a href="/app/stats">
<Link to="../../stats">       => <a href="/stats">
<Link to="../../../stats">    => <a href="/stats">

要するにcd コマンドのように指定できるということです。

参考

<Link>についてはもう一つ注意すべき変更点がありますので、最後に言及しておきます。

<Link>componentプロパティが廃止されました。componentプロパティはデフォルトのLinkコンポーネントをオーバーライドする仕組みで、特に何らかのデザインシステムを使用している場合に使うプロパティでした。

公式ドキュメント曰く、「いくつかのフックを使うだけで、独自のアクセシブルなリンクコンポーネントを作成することができます」とのこと(ですが、詳細不明)。

参考:

React Router v5でcomponentプロパティを使っていた人は注意して下さい。

もっともMaterial UIなどではMaterial UI側にReact Routerと連携する仕組みが用意されていますので、そちらを利用したほうが簡単だと思います。

まとめ

リリースされたReact Router v6についてまとめました。

今までのReact Routerではところどころあった不自由さが改善されているので、良いアップデートかなと思います。

移行はそんなに難しくはないですが、後方互換が開発中とのことですし、公式がまだ様子見していいよと言っているので待つのも全然ありだと思います。

実は地味に気になっているのがドキュメントの読みづらさで、サンプルコードを読みたいときに一々外部のサービス(StackBlitz)に飛ばなければならないのが非常に面倒です。

このあたりも含めると、個人的にはもう少し待ったほうがいいと感じています。

以上です。

-Webプログラミング
-