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@v6react-router-domreact-routerhistory@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 componentconst { id } = useParams<{id: string}>();assertIsDefined(id);
// helpers/assertIsDefined.tsexport 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}`); }}
Link の to から state を渡せない、state を指定するとエラーが出る
state prop から渡す形式になりました。
<Link to={{ pathname: "/list" }} state={data} ><Link to={{ pathname: "/list", state: data }}> List</Link>
NavLink の型エラー
<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 pageconst Users = () => { const match = useRouteMatch(); return ( <Switch> <Route path={`${match.path}/:id`}> <UserProfile /> </Route> </Switch> );};
// v6<Route path="/users/*" element={<Users />} />;// users pageconst 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 を使うべきか
現在の URL と一致する Link をハイライトしたい
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.ts
やtypes/react-router-dom.ts
のような名前で保存しましょう。
import { Location } from "react-router-dom";
declare module "react-router-dom" { export function useLocation<T = unknown>(): Location & { state: T };}