主分支
分支
主分支 (6.23.1)开发分支
版本
6.23.1v4/5.xv3.x
createBrowserRouter
本页内容

createBrowserRouter

这是所有 React Router Web 项目推荐的路由器。它使用 DOM History API 来更新 URL 并管理历史堆栈。

它还支持 v6.4 数据 API,例如 加载器操作获取器 等等。

由于数据 API 的设计将获取和渲染解耦,因此您应该在 React 树之外使用静态定义的路由集创建路由器。有关此设计的更多信息,请参阅 Remixing React Router 博客文章和 When to Fetch 会议演讲。

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

一个 Route 对象数组,在 children 属性上嵌套路由。

createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    loader: rootLoader,
    children: [
      {
        path: "events/:id",
        element: <Event />,
        loader: eventLoader,
      },
    ],
  },
]);

basename

应用程序的基路径,用于您无法部署到域根目录,而需要部署到子目录的情况。

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 延迟活动 fetcher 清理,直到它们返回到 idle 状态。
v7_normalizeFormMethod useNavigation().formMethod 规范化为大写 HTTP 方法。
v7_partialHydration 支持服务器渲染应用程序的局部水合。
v7_prependBasename 将路由器基本名称附加到导航/获取路径。
v7_relativeSplatPath 修复 splat 路由中相对路径解析的错误。
unstable_skipActionErrorRevalidation 如果操作返回 4xx/5xx Response,则默认情况下不会重新验证。

hydrationData

服务器渲染 并且 选择退出自动水合 时,hydrationData 选项允许您从服务器渲染中传入水合数据。这几乎总是从 handler.query 返回的 StaticHandlerContext 值中获取的数据子集。

const router = createBrowserRouter(routes, {
  hydrationData: {
    loaderData: {
      // [routeId]: serverLoaderData
    },
    // may also include `errors` and/or `actionData`
  },
});

局部水合数据

您几乎总是会包含一组完整的 loaderData 来水合服务器渲染的应用程序。但在高级用例(例如 Remix 的 clientLoader)中,您可能希望只包含某些在服务器上渲染的路由的 loaderData。如果您想启用局部 loaderData 并选择加入粒度 route.HydrateFallback 使用,则需要启用 future.v7_partialHydration 标志。在此标志之前,任何提供的 loaderData 都被认为是完整的,并且不会导致在初始水合时执行路由加载器。

当指定此标志时,加载器将在初始水合时在两种情况下运行:

  • 没有提供水合数据。
    • 在这些情况下,HydrateFallback 组件将在初始水合时渲染。
  • 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

这是一个低级 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 仅在需要时以及如果您没有传递自己的 handlerOverride 函数参数时才会在内部调用 handler
    • 请参阅下面的示例,了解如何通过 match.resolve 实现自定义处理程序执行。
  • match.shouldLoad - 一个布尔值,指示此路由处理程序是否需要在此传递中调用。
    • matches 数组始终包含所有匹配的路由,即使只有某些路由处理程序需要调用,以便可以实现诸如中间件之类的功能。
    • shouldLoad 通常只有在您完全跳过路由处理程序并实现自定义处理程序逻辑时才有趣——因为它可以让您确定是否应该为此路由运行该自定义逻辑。
    • 例如:
      • 如果您位于 /parent/child/a 并导航到 /parent/child/b——您将获得一个包含三个匹配项的数组([parent, child, b]),但只有 b 将具有 shouldLoad=true,因为 parentchild 的数据已经加载。
      • 如果您位于 /parent/child/a 并提交到 aaction,那么只有 a 将具有 shouldLoad=true,用于 dataStrategy 的操作执行。
        • 在操作之后,将再次为 loader 重新验证调用 dataStrategy,并且所有匹配项都将具有 shouldLoad=true(假设没有自定义 shouldRevalidate 实现)。

dataStrategy 函数应返回一个并行的 HandlerResult 实例数组,指示处理程序是否成功。如果返回的 handlerResult.result 是一个 Response,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 不同的窗口。