<BrowserRouter>
and <Routes>
當我們最初開始將 Remix 資料 API 導入 React Router 時,我們發現它們提供了一個相當不同的路由結構方式。與透過 <Routes>
組件在 React 渲染組件樹時 探索路由不同,我們需要提升路由定義,才能夠 將擷取與渲染分開。
這帶來了一個有趣的難題。我們已擁有許多 v6 BrowserRouter
應用程式,可以透過 <Routes>
組件開心定義其路由,我們如何能提供他們順暢的升級體驗,而不需要全面性的遷移至新方法?這排除了新的主要版本,我們專注於以完全向後相容的方式加入這些新功能,提供使用者從 BrowserRouter
至 RouterProvider
的漸進升級路徑。
首先要注意的是,存在一些新的資料 API,僅適用于透過新的資料路由器(例如 createBrowserRouter
)定義的路由。這些 API 包含幾種類別
loader
、action
、shouldRevalidate
、handle
和 lazy
useLoaderData
、useActionData
、useFetcher
、useMatches
、useNavigation
等route.errorElement
、route.ErrorBoundary
和 useRouteError
v6.4.0 之前的其他 API 仍然可用於兩個BrowserRouter
和RouterProvider
應用程式。這些包含常用鉤子/元件,例如 useNavigate
、useLocation
、useParams
、<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>
);
}
我們只需做一些小更動,就能在 RouterProvider
內呈現此應用程式
App
元件變更為 Root
<BrowserRouter>
元件Root
元素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,我們需要開始將路由逐一提升到資料路由器。
我們從 <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 加入家庭路由(loader
、action
、errorElement
),並開始在 Home 元件內運用資料鉤子(useLoaderData
、useActionData
、useFetcher
、<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
。您可能想先提升佈局路由,讓所有子代都能在其內嵌套。