分支
主 (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> 樹狀結構。這明確地允許從一個逐步移轉到另一個。我們來看看我們將如何執行此操作。

目前應用程式

假設我們有一個具有 2 個后代路由樹狀結構的目前應用程式,並假設這些路由都執行元件內資料提取,並呈現在自己的載入和錯誤狀態中。

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>
  );
}

使用根 splart 路由新增 RouterProvider

我們只需做一些小更動,就能在 RouterProvider 內呈現此應用程式

  1. 將目前的 App 元件變更為 Root
  2. 移除 <BrowserRouter> 元件
  3. 使用 splart 路由建立資料路由單體,用於 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> 外圍呈現應用程式 shell

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,但這個「應用程式 shell」並非路由的一部分... 但它可以是!這個「應用程式 shell」實際上只是一個具有 <Outlet> 的佈局路由而已!因此,在開始上述遷移之前,請先將這個「應用程式 shell」移到一個沿著您的路由的無路徑佈局路由中,如下所示

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。您可能想先提升佈局路由,讓所有子代都能在其內嵌套。