2220 文字
11 分

Q. Migrate to React Router v6 Q&A まとめ

公式のマイグレーションガイドに記載されている内容については、補完するような内容のみを記載しています。

Router の書き方は 3 種類ある: Data Router, Router, useRoutes#

大きく分けて以下の 3 パターンの記述ができます。各々のパターンの情報を見ていると混乱する可能性があるので、それがどのパターンの話なのか認識するようにしてください。

  • Data Router: RouterProvider, createBrowserRouter を使っている場合
  • Router
  • useRoutes

現在、BrowserRouter や Router を利用しているのであれば Router をそのまま利用した方が、移行が楽だと思います。ただし既に react-router-dom v6 を利用しているリポジトリのほとんどは useRoutes を利用しています。

react-router-dom 以外削除する、 @types/react-router-dom v6 がない、v6 の記述にすると型エラーが出る#

v6 から react-router-dom だけをインストールすれば良くなりました。

react-router-dom@v6
react-router-dom
react-router
history
@types/react-router-dom
@types/react-router

@types/react-router や@types/react-router-dom が入っていると、react-router-dom へ同梱されるようになった型と衝突するため、削除する必要があります。

react-router と history から import しない#

必要なものは react-router-dom から import 出来ます。react-router-dom から import しましょう。

一般に暗黙的にインストールされたパッケージが継続的に利用できるかと、異なるパッケージから export されたものが同一であることは保証できないため、react-router と history からの import は避けた方が良いです。

createBrowserHistory を使わない#

createBrowserHistory は利用できません。

const history = createBrowserHistory({ basename: process.env.BASENAME });
return (
<Router history={history}>
<MyAppRoutes />
</Router>
);

次のように書き換えてください。

<BrowserRouter basename={process.env.BASENAME}>
<MyAppRoutes />
</BrowserRouter>

useParams の返り値が optional になった#

本当にその値が opitonal になることがないのであれば、assert 関数が便利です。

// page component
const { id } = useParams<{id: string}>();
assertIsDefined(id);
// helpers/assertIsDefined.ts
export function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {
if (val === undefined || val === null) {
throw new Error(`Expected value to be defined, but received ${val}`);
}
}

state prop から渡す形式になりました。

<Link to={{ pathname: "/list" }} state={data} >
<Link to={{ pathname: "/list", state: data }}>
List
</Link>
<NavLink
exact
end
activeStyle={{ ... }}
style={({ isActive }) => { ... }}
activeClassName="actived"
className={({ isActive }) => { ... }}
/>

useHistory を useNavigate に置き換える#

https://reactrouter.com/en/main/upgrading/v5#use-usenavigate-instead-of-usehistory

const navigate = useNavigate()
const history = useHistory()
navigate("/home")
history.push("/home")

Redirect を Navigate に置き換える#

Redirect と Navigate はほぼそのまま置き換えられますが、デフォルトの挙動が replace から push に変更されている点には気を付けてください。

<Navigate to="about" replace />
<Redirect to="about" />
<Navigate to="home" />
<Redirect to="home" push />

Redirect 元に戻れなくしたい場合は、明示的に replace を指定する必要があります。

これは Routes の都合ですが、Navigate は Redirect と異なり Switch の代替である Routes 内に含めることが出来ません。

<Switch>
<Redirect from="about" to="about-us" />
</Switch>
<Routes>
<Route path="about" element={<Navigate to="about-us" replace />} />
</Routes>

Switch を Routes に置き換える#

基本的にそのまま置き換えることが出来ます。ネストされた Routes 内では、相対パスが利用できます。

const App = () => {
return (
<BrowserRouter>
<Routes>
<Switch>
<Route path="users/*" element={<Users />} />
<Route path="/users"><Users /></Route>
</Routes>
</Switch>
</BrowserRouter>
)
}
const Users = () => {
const match = useRouteMatch();
return (
<Routes>
<Switch>
<Route path=":id" element={<UserProfile />} />
<Route path={`${match.path}/:id`}><UserProfile /></Route>
</Routes>
</Switch>
)
}

Route を置き換える#

exact がなくなった#

v6 からデフォルトが完全一致になったため、exact は削除されました。完全一致でない場合は* を利用します。

<Route path="/" element={<Home />} />
<Route exact path="/"><Home /></Route>
<Route path="users/*" element={<Users />} />
<Route path="/users"><Users /></Route>

相対的なパス#

相対的なパスが利用できます。今まで複数の Route に入る可能性があるコンポーネントや親のパスを伝えたくないようなケースでは、useRouteMatch などを利用する必要がありましたが、v6 では代わりに相対パスが利用できます。

const match = useRouteMatch();
<Route path=":id" element={<UserProfile />} />
<Route path={`${match.path}/me`}><OwnUserProfile /></Route>

ページコンポーネントの渡し方、element を使う#

v5 では、component prop や render prop、children などの渡し方がありました。v6 では element で渡すことが出来ます。

<Route path="/about" element={<About/>} />
<Route path="/about" component={About} />

型を見ると children でも渡すことが出来るように思えますが、children には Route を渡すことが想定されるので、element を利用してください。

ネストしたルートでページ遷移できなくなった#

次のような移行をした場合、/users/:id以下のパスへの遷移が出来なくなります。移動しようとすると、親ルートの* にキャッチされ、このケースでは/aboutに遷移します。

// v5
<Route path="/users/:id" component={UsersPageHasNestedRoutes} />
<Route exact path="/about" component={AboutPage} />
<Route path="*" render={() => <Redirect to="about" />} />
// v6
<Route path="/users/:id" element={<UsersPageHasNestedRoutes />} />
<Route path="/about" element={<AboutPage/>} />
<Route path="*" element={<Navigate to="about" replace />} />

完全一致が期待されない v5 で exact を意図して付けていなかったパスでは/*を末尾に付ける必要があります。

<Route path="/users/:id/*" element={<UsersPageHasNestedRoutes />} />
<Route path="/users/:id" element={<UsersPageHasNestedRoutes />} />
<Route path="/about" element={<AboutPage/>} />
<Route path="*" element={<Navigate to="about" replace />} />

また index ページが存在しないのであれば、次のように書けます。

<Route path="/users/:id/*" element={<UsersPageHasNestedRoutes />} />
<Route path="/about" element={<AboutPage/>} />
<Route index element={<Navigate to="about" replace />} />
<Route path="*" element={<Navigate to="about" replace />} />

さらに次のようにルート構造を巻き上げれば、element に Route が含まれているかどうかを気にする必要がなくなります。

<Route path="/users/:id">
<Route index element={<UserPage />} />
<Route path="info" element={<UserInfoPage />} />
<Route path="code" element={<UserCodePage}/>} />
</Route>
<Route path="/users/:id/*" element={<UsersPageHasNestedRoutes />} />
<Route path="/about" element={<AboutPage/>} />
<Route index element={<Navigate to="about" replace />} />

ルート構造を巻き上げる、親ルートでネストルートを宣言する#

ルート構造を巻き上げた方が管理しやすくなります。React Route v6 では Nested Routes を次のように宣言できます。Data Router や useRoutes を使えばよりスマートに書けます。

<Routes>
<Route index element={<Navigate to="/questions" replace />} />
<Route path="/questions" elemenet={<Outlet/>}>
<Route index element={<FormListPage />} />
<Route path=":questionId" elemenet={<Outlet/>}>
<Route index element={<EditPage />}>
<Route path="answers" element={<AnswersPage />}>
<Route path="debug" element={<DebugPage />}>
</Route>
</Route>
</Routes>

Outlet を使う、ルート毎に異なるヘッダーやレイアウトを提供したい#

https://reactrouter.com/en/main/components/outlet

Outlet は Route で指定された element と置き換わります。パスに応じてヘッダーやフッターを切り替えたいといったケースで便利です。

function DefaultLayout() {
return (
<div>
<AppHeader />
<Outlet />
<AppFooter />
</div>
);
}
function App() {
return (
<Routes>
<Route path="/" element={<DefaultLayout />}>
<Route path="users" element={<UsersPage />} />
<Route path="about" element={<AboutPage />} />
</Route>
</Routes>
);
}

useRouteMatch を useMatch に置き換える#

https://reactrouter.com/en/main/upgrading/v5#replace-useroutematch-with-usematch

置き換えろと軽く書かれていますが、パターン引数が必須になり、配列を渡せなくなるなったため、同じような感じでは利用できません。次の Q&A を参考にして移行してください。

// v5
<Route path="/users">
<Users />
</Route>;
// users page
const Users = () => {
const match = useRouteMatch();
return (
<Switch>
<Route path={`${match.path}/:id`}>
<UserProfile />
</Route>
</Switch>
);
};
// v6
<Route path="/users/*" element={<Users />} />;
// users page
const Users = () => {
const match = useMatch(/* Error!!! Require path pattern! */);
//const match = useMatch("/users/:id")
return (
<Routes>
<Route path={`${match.path}/:id`} element={<UserProfile />} />
</Routes>
);
};

useRouteMatch の代わりに useMatch を使うべきか#

NavLink を使いましょう。

ネストしたルートで親のパスが知りたい#

相対パスを使いましょう。

パラメーターがほしい#

useParams を使いましょう。

パスの情報がほしい#

useLocation()useResolvedPath(””)を検討してください。

Data Router を利用している場合#

useMatches で代替できます。BrowserRouter などを利用している場合、使用できません。

useRoutes を利用している場合#

BrowserRouter などを利用している場合とは異なり、親要素の routes がオブジェクトで定義されていることが期待されるため、次のようなコードを書けば useRouteMatch の代替できます。

const useMyRouteMatch = (routes: RouteObjectType[]) => {
const location = useLocation();
const [{ route }] = matchRoutes(routes, location);
return route.path;
};

複数のパターンにマッチしているかを見たい#

pattern は自分で指定する必要がありますが、以下のような記述をすれば useRouteMatch に比較的近い使用感で利用できると思います。

const useMyRouteMatch = (patterns: PathPattern<Path> | Path) => {
const { pathname } = useLocation();
const match = useMemo(() => {
return patterns.find((path) => !!matchPath(path, pathname));
}, [pathname]);
return match;
};

useLocation の state に as を使わずに型を付ける#

https://github.com/remix-run/react-router/pull/7326#issuecomment-626418225

次のようなファイルをreact-router-dom.d.tstypes/react-router-dom.ts のような名前で保存しましょう。

import { Location } from "react-router-dom";
declare module "react-router-dom" {
export function useLocation<T = unknown>(): Location & { state: T };
}
Q. Migrate to React Router v6 Q&A まとめ
https://blog.ohirunewani.com/posts/migrate-to-react-router-v6/
作者
hrdtbs
公開日
2023-07-10
ライセンス
CC BY-NC-SA 4.0