主分支
分支
main (6.23.1)dev
版本
6.23.1v4/5.xv3.x
迁移到 RouterProvider
本页内容

迁移到 RouterProvider

当我们最初开始将 Remix 数据 API 迁移到 React Router 时,我们意识到它们带来了构建路由的一种截然不同的方式。我们不再通过 <Routes> 组件在 React 渲染组件树时发现路由,而是需要提升路由定义,以便我们能够 将获取数据与渲染解耦

这带来了一个有趣的难题。我们有大量的 v6 BrowserRouter 应用程序,它们通过 <Routes> 组件愉快地定义了它们的路由——我们如何为它们提供平滑的升级体验,而无需进行大规模迁移到新方法?这排除了新的主要版本,我们专注于以完全向后兼容的方式添加这些新功能,这将为用户提供从 BrowserRouterRouterProvider增量升级路径。

差异

首先要注意的是,存在一些新的 数据 API,它们只适用于通过新的 数据路由器(即 createBrowserRouter定义的路由。这些 API 包括以下几类:

  • 路由级数据 API,例如 loaderactionshouldRevalidatehandlelazy
  • 组件内数据钩子,例如 useLoaderDatauseActionDatauseFetcheruseMatchesuseNavigation
  • 错误处理 API,例如 route.errorElementroute.ErrorBoundaryuseRouteError

在 v6.4.0 之前存在的其余 API 仍然可以在两种 BrowserRouterRouterProvider 应用程序中使用。这些包括常见的钩子/组件,例如 useNavigateuseLocationuseParams<Link><Outlet /> 等。

迁移

我们构建了新的 <RouterProvider> 组件,以便它能够在根路由器定义的路由上启用新的数据 API,同时不排除 BrowserRouter 应用程序中常用的后代 <Routes> 树。这样做是为了明确地允许从一种方式到另一种方式的增量迁移。让我们看看如何做到这一点。

当前应用程序

假设我们有一个当前应用程序,它有两个后代路由树,并假设这些路由都在进行组件内数据获取,并渲染它们自己的加载和错误状态。

import {
  BrowserRouter,
  Link,
  Route,
  Routes,
} from "react-router-dom";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/blog/*" element={<BlogApp />} />
        <Route path="/users/*" element={<UserApp />} />
      </Routes>
    </BrowserRouter>
  );
}

function Home() {
  return (
    <>
      <h1>Welcome!</h1>
      <p>
        Check out the <Link to="/blog">blog</Link> or the{" "}
        <Link to="users">users</Link> section
      </p>
    </>
  );
}

function BlogApp() {
  return (
    <Routes>
      <Route index element={<h1>Blog Index</h1>} />
      <Route path="posts" element={<h1>Blog Posts</h1>} />
    </Routes>
  );
}

function UserApp() {
  return (
    <Routes>
      <Route index element={<h1>Users Index</h1>} />
    </Routes>
  );
}

添加 RouterProvider 和一个根通配符路由

我们只需进行一些小的更改,就可以将此应用程序渲染到 RouterProvider 中。

  1. 将当前的 App 组件更改为 Root
  2. 删除 <BrowserRouter> 组件
  3. 使用通配符路由为 Root 元素创建一个数据路由器单例
  4. 添加一个新的 App 组件,渲染一个 <RouterProvider>
import {
  createBrowserRouter,
  Link,
  Route,
  RouterProvider,
  Routes,
} from "react-router-dom";

// 3️⃣ Router singleton created
const router = createBrowserRouter([
  { path: "*", element: <Root /> },
]);

// 4️⃣ RouterProvider added
export default function App() {
  return <RouterProvider router={router} />;
}

// 1️⃣ Changed from App to Root
function Root() {
  // 2️⃣ `BrowserRouter` component removed, but the <Routes>/<Route>
  // component below are unchanged
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/blog/*" element={<BlogApp />} />
      <Route path="/users/*" element={<UserApp />} />
    </Routes>
  );
}

function Home() {
  /* Unchanged */
}
function BlogApp() {
  /* Unchanged */
}
function UserApp() {
  /* Unchanged */
}

🥳 恭喜 - 您现在正在渲染一个数据路由器应用程序!但是等等 - 我们还不能使用任何新功能,因为我们的路由都没有在顶部使用 createBrowserRouter 定义 😢。要访问新的 API,我们需要开始逐个将路由提升到数据路由器。

开始提升路由并利用数据 API

让我们从 <Home> 元素的 / 路由开始。我们只需要将 <Route> 定义提升到数据路由器即可

const router = createBrowserRouter([
  { path: "/", element: <Home /> }, // 🆕
  { path: "*", element: <Root /> },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

function Root() {
  return (
    <Routes>
      {/* ⬆️ Home route lifted up to the data router */}
      <Route path="/blog/*" element={<BlogApp />} />
      <Route path="/users/*" element={<UserApp />} />
    </Routes>
  );
}

现在,您可以将数据 API 添加到您的主页路由(loaderactionerrorElement),并开始在您的 Home 组件中利用数据钩子(useLoaderDatauseActionDatauseFetcher<Form> 等)。

现在让我们看看如何将博客应用程序向上提升,但仍然一次提升一个叶子路由。为了将 /blog 索引路由提升,我们需要将 /blog/* 通配符路由也提升,但我们仍然可以将 /blog/posts 路由渲染在当前位置,并单独进行处理。

const router = createBrowserRouter([
  { path: "/", element: <Home /> },
  {
    // Lifted blog splat route
    path: "/blog/*",
    children: [
      // New blog index route
      { index: true, element: <h1>Blog Index</h1> },
      // Blog subapp splat route added for /blog/posts matching
      { path: "*", element: <BlogApp /> },
    ],
  },
  { path: "*", element: <Root /> },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

function Root() {
  return (
    <Routes>
      {/* ⬆️ Blog splat route lifted */}
      <Route path="/users/*" element={<UserApp />} />
    </Routes>
  );
}

function BlogApp() {
  return (
    <Routes>
      {/* ⬆️ Blog index route lifted */}
      <Route path="posts" element={<h1>Blog Posts</h1>} />
    </Routes>
  );
}

现在,您的博客索引路由可以参与数据加载了。

您可以继续这样做,一次提升一个路由,直到最终将所有路由转换为数据路由,并且不再使用任何嵌套的 <Routes> 来定义您的路由树。为了避免捆绑包膨胀,建议利用 route.lazy 属性来延迟加载您的路由。

常见问题解答

但是我在 <BrowserRouter><Routes> 之间有一些东西

许多人通过以下方式在他们的 <Routes> 周围渲染一个应用程序外壳

export default function App() {
  return (
    <BrowserRouter>
      <header>
        <h1>My Super Cool App</h1>
        <NavMenu />
      </header>
      <main>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/blog/*" element={<BlogApp />} />
          <Route path="/users/*" element={<UserApp />} />
        </Routes>
      </main>
      <footer>©️ me 2023</footer>
    </BrowserRouter>
  );
}

如果您发现自己处于这种情况,请不要担心 - 您可以开始上述迁移之前执行一个简单的解决方案。

这很常见,但在上述迁移方法中存在问题,因为我们需要将东西逐个提升到 RouterProvider,但这个“应用程序外壳”不是路由的一部分……但它可以是!这个“应用程序外壳”实际上只是一个带有 <Outlet> 的无路径布局路由!因此,在开始上述迁移之前,只需将这个“应用程序外壳”移动到您的路由周围的无路径布局路由中,如下所示

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* 1️⃣ Wrap your routes in a pathless layout route */}
        <Route element={<Layout />}>
          <Route path="/" element={<Home />} />
          <Route path="/blog/*" element={<BlogApp />} />
          <Route path="/users/*" element={<UserApp />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Layout() {
  return (
    <>
      <header>
        <h1>My Super Cool App</h1>
        <NavMenu />
      </header>
      <main>
        {/* 2️⃣ Render the app routes via the Layout Outlet */}
        <Outlet />
      </main>
      <footer>©️ me 2023</footer>
    </>
  );
}

完成此操作后,您可以继续使用上述迁移策略,并开始将路由逐个提升到您的 RouterProvider 中。您可能希望首先提升布局路由,以便所有子路由都嵌套在其中。