主項目
分支
主項目 (6.23.1)開發版
版本
6.23.1v4/5.xv3.x
createBrowserRouter
在此頁面

createBrowserRouter

建議用於所有 React Router 網路計畫的路由器。它使用 DOM 歷程 API 來更新 URL 並管理歷程堆疊。

它也啟用了 v6.4 資料 API 如 載入器動作擷取器 等。

由於資料 API 中擷取和渲染的解耦,你應該在 React 樹外部建立你的路由器,且路由器應為靜態定義的路由組。如需瞭解更多此設計的資訊,請參閱 Remixing React Router 部落格文章和 何時擷取 會議演講。

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

部分 Hydration 資料

你幾乎總是會包含一組完整的loaderData來 hydration 伺服器呈現的應用程式。但在進階使用案例(例如 Remix 的clientLoader),你可能只想包含在伺服器上呈現的某部分路由的loaderData。如果要啟用部分loaderData,並選擇細化的route.HydrateFallback用法,你將需要啟動future.v7_partialHydration標記。在此標記之前,會假設任何提供的 loaderData 是完整的,而且不會在初始 hydration 時執行路由 loader。

當指定此標記,loader 將在 2 種情況下於初始 hydration 時執行

  • 未提供 hydration 資料
    • 在這些情況下,HydrateFallback元件將在初始的 hydration 上顯示
  • loader.hydrate屬性設定為true
    • 這樣即使你沒有在初始 hydration(例如,使用 hydration 資料填滿快取)上呈現後備資料,你也可以執行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

這是一個低階層級的 API,適用於進階使用案例。這會覆寫 React Router 對loader/action執行的內部處理,而且如果操作錯誤會中斷應用程式程式碼。請謹慎使用並執行適當的測試。

此 API 被標示為「不穩定」故小版本更新時其 API 有可能會產生重大變更

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 相同的引數(requestparams),但它也接收一個 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,因為 parentchild 的資料已載入完成
      • 如果您在 /parent/child/a 並提交至 aaction,表示僅 a 會有 shouldRevalidate 實作時,對 dataStrategy 動作執行 shouldLoad=true
        • action 之後,將再次針對 loader 重新驗證呼叫 dataStrategy,所有匹配都會有 shouldLoad=true(假設沒有自訂 shouldRevalidate 實作)

dataStrategy 函數應傳回 HandlerResult 實例的平行陣列,此陣列表示處理程序是否成功。如果傳回的 handlerResult.resultResponse,React Router 會為你解開它包裝 (經由 res.jsonres.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