createBrowserRouter
建議用於所有 React Router 網路計畫的路由器。它使用 DOM 歷程 API 來更新 URL 並管理歷程堆疊。
它也啟用了 v6.4 資料 API 如 載入器、動作、擷取器 等。
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import Root, { rootLoader } from "./routes/root";
import Team, { teamLoader } from "./routes/team";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
function createBrowserRouter(
routes: RouteObject[],
opts?: {
basename?: string;
future?: FutureConfig;
hydrationData?: HydrationState;
window?: Window;
}
): RemixRouter;
routes
路由
物件的陣列,children
屬性有巢狀路由。
createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "events/:id",
element: <Event />,
loader: eventLoader,
},
],
},
]);
路徑基底
如果無法建置到網域根目錄,而是子目錄,應用程式的路徑基底。
createBrowserRouter(routes, {
basename: "/app",
});
連結到根目錄時,會保留尾端斜線
createBrowserRouter(routes, {
basename: "/app",
});
<Link to="/" />; // results in <a href="/app" />
createBrowserRouter(routes, {
basename: "/app/",
});
<Link to="/" />; // results in <a href="/app/" />
future
針對此路由啟用的未來標記的選用組。我們建議盡早選擇採用新發布的未來標記,而非拖到最後,以簡化你將來升級至 v7 的作業。
const router = createBrowserRouter(routes, {
future: {
// Normalize `useNavigation()`/`useFetcher()` `formMethod` to uppercase
v7_normalizeFormMethod: true,
},
});
目前提供以下未來標記
標記 | 說明 |
---|---|
v7_fetcherPersist |
延遲移除 active fetcher,直到它們返回至idle 狀態 |
v7_normalizeFormMethod |
將useNavigation().formMethod 正規化為大寫的 HTTP 方法 |
v7_partialHydration |
支援伺服器呈現應用的部分 hidratin |
v7_prependBasename |
將路由 basename 加到 navigate/fetch 路徑前 |
v7_relativeSplatPath |
修正 splat 路由中的瑕疵相對路徑解析 |
unstable_skipActionErrorRevalidation |
如果動作回傳 4xx/5xx 回應 ,預設不重新驗證 |
hydrationData
在伺服器呈現和選用停用自動 hydration時,hydrationData
選項允許你從伺服器呈現傳遞 hydration 資料。這幾乎總是會是從 handler.query 取得的 StaticHandlerContext
值的子資料集
const router = createBrowserRouter(routes, {
hydrationData: {
loaderData: {
// [routeId]: serverLoaderData
},
// may also include `errors` and/or `actionData`
},
});
你幾乎總是會包含一組完整的loaderData
來 hydration 伺服器呈現的應用程式。但在進階使用案例(例如 Remix 的clientLoader
),你可能只想包含在伺服器上呈現的某部分路由的loaderData
。如果要啟用部分loaderData
,並選擇細化的route.HydrateFallback
用法,你將需要啟動future.v7_partialHydration
標記。在此標記之前,會假設任何提供的 loaderData
是完整的,而且不會在初始 hydration 時執行路由 loader。
當指定此標記,loader 將在 2 種情況下於初始 hydration 時執行
HydrateFallback
元件將在初始的 hydration 上顯示loader.hydrate
屬性設定為true
loader
const router = createBrowserRouter(
[
{
id: "root",
loader: rootLoader,
Component: Root,
children: [
{
id: "index",
loader: indexLoader,
HydrateFallback: IndexSkeleton,
Component: Index,
},
],
},
],
{
future: {
v7_partialHydration: true,
},
hydrationData: {
loaderData: {
root: "ROOT DATA",
// No index data provided
},
},
}
);
unstable_dataStrategy
loader
/action
執行的內部處理,而且如果操作錯誤會中斷應用程式程式碼。請謹慎使用並執行適當的測試。
React Router 預設對於您的資料載入/提交方式有意見-最重要的是並行執行您的所有載入器以達到最佳資料擷取。雖然我們認為對大多數使用案例來說這是正確的行為,但我們意識到對於廣泛的應用程式需求而言,資料擷取並沒有「一體適用」的解決方案。
unstable_dataStrategy
選項讓您完全控制您的載入器和動作如何執行,也為打造進階 API(例如中介軟體、內容和快取層)奠定基礎。久而久之,我們預期我們會在內部運用此 API 為 React Router 帶來更多一流水準 API,但在那之前(還有之後),這是您為您的應用程式資料需求加入更進階功能的方式。
interface DataStrategyFunction {
(args: DataStrategyFunctionArgs): Promise<
HandlerResult[]
>;
}
interface DataStrategyFunctionArgs<Context = any> {
request: Request;
params: Params;
context?: Context;
matches: DataStrategyMatch[];
}
interface DataStrategyMatch
extends AgnosticRouteMatch<
string,
AgnosticDataRouteObject
> {
shouldLoad: boolean;
resolve: (
handlerOverride?: (
handler: (ctx?: unknown) => DataFunctionReturnValue
) => Promise<HandlerResult>
) => Promise<HandlerResult>;
}
interface HandlerResult {
type: "data" | "error";
result: any; // data, Error, Response, DeferredData
status?: number;
}
unstable_dataStrategy
接收和 loader
/action
相同的引數(request
、params
),但它也接收一個 matches
陣列,該陣列是已匹配路由的陣列,其中每一個匹配都延伸並包含兩個新欄位,供資料策略函數使用
match.resolve
- 非同步函數,用來解析任何 route.lazy
實作並執行路由的處理常式(必要的話),回傳 HandlerResult
match.resolve
,以確保所有延遲路由都能適當解析resolve
僅會在必要時在內部呼叫 handler
,而且如果您沒有傳遞您的 handlerOverride
函數參數match.resolve
實作自訂處理常式執行,請參閱以下範例match.shouldLoad
- 布林值,指出此路由處理常式是否需要在本傳遞中呼叫matches
陣列始終包括所有已匹配路由,即使僅需呼叫部分路由處理常式,才能實作類似中介軟體的功能shouldLoad
感到有興趣,因為這樣您便可以判斷該自訂邏輯是否需要對此路由執行/parent/child/a
並導覽至 /parent/child/b
,您將會取得三個匹配陣列([parent, child, b]
),但僅有 b
會有 shouldLoad=true
,因為 parent
和 child
的資料已載入完成/parent/child/a
並提交至 a
的 action
,表示僅 a
會有 shouldRevalidate
實作時,對 dataStrategy
動作執行 shouldLoad=true
action
之後,將再次針對 loader
重新驗證呼叫 dataStrategy
,所有匹配都會有 shouldLoad=true
(假設沒有自訂 shouldRevalidate
實作)dataStrategy
函數應傳回 HandlerResult
實例的平行陣列,此陣列表示處理程序是否成功。如果傳回的 handlerResult.result
是 Response
,React Router 會為你解開它包裝 (經由 res.json
或 res.text
)。如果你需要自訂解碼 Response
但保留狀態碼,你可以傳回 handlerResult.result
中的已解碼值,並透過 handlerResult.status
傳送狀態 (例如,使用 future.unstable_skipActionRevalidation
標誌時)。如果你沒有傳遞處理程序覆寫函數,則 match.resolve()
會傳回 HandlerResult
。如果你有傳遞,你需要將 handler
結果包裝在 HandlerResult
中 (請參閱以下範例)。
在最簡單的情況下,讓我們了解如何連接這個 API,以便在我們的路由載入程式/動作執行時新增一些記錄
let router = createBrowserRouter(routes, {
unstable_dataStrategy({ request, matches }) {
return Promise.all(
matches.map(async (match) => {
console.log(`Processing route ${match.route.id}`);
// Don't override anything - just resolve route.lazy + call loader
let result = await match.resolve();
console.log(
`Done processing route ${match.route.id}`
);
return result;
})
);
},
});
我們透過 handle
為每個路由定義一個中間件,並按順序呼叫中間件,然後並行呼叫所有載入程式 - 提供中間件提供的任何可用資料
const routes = [
{
id: "parent",
path: "/parent",
loader({ request }, context) {
/*...*/
},
handle: {
async middleware({ request }, context) {
context.parent = "PARENT MIDDLEWARE";
},
},
children: [
{
id: "child",
path: "child",
loader({ request }, context) {
/*...*/
},
handle: {
async middleware({ request }, context) {
context.child = "CHILD MIDDLEWARE";
},
},
},
],
},
];
let router = createBrowserRouter(routes, {
async unstable_dataStrategy({
request,
params,
matches,
}) {
// Run middleware sequentially and let them add data to `context`
let context = {};
for (const match of matches) {
if (match.route.handle?.middleware) {
await match.route.handle.middleware(
{ request, params },
context
);
}
}
// Run loaders in parallel with the `context` value
return Promise.all(
matches.map((match, i) =>
match.resolve(async (handler) => {
// Whatever you pass to `handler` will be passed as the 2nd parameter
// to your loader/action
let result = await handler(context);
return { type: "data", result };
})
)
);
},
});
你甚至可以在路由層級不想定義載入器實作。你可能只想決定路由,並為所有資料發出單一 GraphQL 要求?你可以透過設定 route.loader=true
來做到這一點,因此它符合「有載入器」,然後將 GQL 片段儲存在 route.handle
const routes = [
{
id: "parent",
path: "/parent",
loader: true,
handle: {
gql: gql`
fragment Parent on Whatever {
parentField
}
`,
},
children: [
{
id: "child",
path: "child",
loader: true,
handle: {
gql: gql`
fragment Child on Whatever {
childField
}
`,
},
},
],
},
];
let router = createBrowserRouter(routes, {
unstable_dataStrategy({ request, params, matches }) {
// Compose route fragments into a single GQL payload
let gql = getFragmentsFromRouteHandles(matches);
let data = await fetchGql(gql);
// Parse results back out into individual route level HandlerResult's
let results = parseResultsFromGql(data);
return results;
},
});
window
對於瀏覽器開發人員工具外掛程式或測試等環境,很適用於使用不同的視窗而非全域 window
。