RouterProvider

Tip

RouterProvider是React Router v6.4版本引入的新功能,在使用的时候需要主机自己项目中所使用的React Router的版本。

从 React Router v6.4 版本开始,React Router 引入了一套新的路由定义方法:Data API。这种方法所创建出来的直接就是 Router。根据应用中所要使用的不同种类的 Router,目前总共有三个函数可供使用。

  • createBrowserRouter(),用于创建一个BrowserRouter,对应使用<BrowserRouter>组件创建的路由。
  • createMemoryRouter(),用于创建一个MemoryRouter,对应使用<MemoryRouter>组件创建的路由。
  • createHashRouter(),用于创建一个HashRouter,对应使用<HashRouter>组件创建的路由。

Warning

传统使用<Router>系列组件和<Routes>系列组件创建路由的形式不适用Data API。并且这两种API的使用形式不能在应用中同时存在。

Data API 接受一个数组来完成路由的定义,例如使用createBrowserRouter()来创建之前的最小应用,就是以下样子。

const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
  },
  {
    path: "/about",
    element: <About />,
  },
]);

ReactDOM.createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />
);

这样创建好的路由就需要使用<RouterProvider>注入到应用中,就如同上例中所示的那样。

同样以createBrowserRouter()为例,这个函数所接受的参数主要有以下这些。

  • routesRouteObject[]类型,用于定义整套路由。
  • opts,路由组件的配置,主要可以配置以下两项内容。
    • basename,自动在所有路由路径前添加的基础路径名称。例如设置为/app,那么在访问/的时候,实际上会自动指向/app
    • window,设定是否要导航到一个新的窗口中。

在 Data API 中最常用的就是RouteObject[]了,这是在 Data API 中用来定义路由的核心。其实RouteObject的结构也十分简单,基本上跟<Route>组件所接受的参数相同。这里给出RouteObject的类型定义。

interface RouteObject {
  /** 路由路径 */
  path?: string;
  /** 路由是否是默认的根路由 */
  index?: boolean;
  /** 子路由配置 */
  children?: React.ReactNode | RouteObject[];
  /** 是否要使用大小写敏感的匹配 */
  caseSensitive?: boolean;
  /** 用于在组件树上下文中标记loader方法加载到的数据 */
  id?: string;
  /** 用于在路由加载之前提前加载一些数据的方法 */
  loader?: LoaderFunction;
  /** 用于响应form提交的事件 */
  action?: ActionFunction;
  /** 当前路由需要渲染的组件 */
  element?: React.ReactNode | null;
  /** 当`loader`或者`action`中出现错误的时候需要渲染的组件,
   * 可以作为React中的错误边界(Error Boundary)使用。
   */
  errorElement?: React.ReactNode | null;
  handle?: RouteObject["handle"];
  /**  确定什么情况下需要重新执行`loader`来重新刷新数据。 */
  shoudeRevalidate?: ShouldRevalidateFunction;
}

为了使用传统<Routes>系列组件的应用能够更快的转换到使用 Data API,React Router 还提供了一套函数来将<Routes>系列组件定义的路由转换成 Data API 中使用的RouteObject[]数组对象。这个函数就是createRoutesFromElements(),使用的时候直接将<Routes>组件传入即可。

嵌套路由定义

RouteObject中定义嵌套路由也是非常容易的,跟使用<Routes>系列组件一样。以下是一个嵌套路由的定义示例。

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    loader: rootDataLoader,
    children: [
      {
        index: true,
        element: <Glance />,
      },
      {
        path: "about",
        element: <About />,
      },
      {
        path: "user",
        element: <UserList />,
      },
      {
        path: "user/:uid",
        element: <UserDetail />,
        loader: userDetailLoader,
      },
    ],
  },
]);

React Router 中的子路由可以嵌套多层,每一层children中定义的子路由都将渲染到这一层element中组件的<Outlet>组件中,所以在定义的时候可以根据实际应用中布局来进行定义。

loader方法

路由上定义的loader方法的主要功能就是在路由组件渲染之前加载路由组件中需要展示的数据。例如以下示例中可以在路由加载之前获取需要展示的用户信息。

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    children: [
      {
        path: "/user/:uid",
        element: <UserDetail />,
        loader: async ({ params }) => {
          return await fetch.get(`/api/user/${params.uid}`);
        },
      },
    ],
  },
]);

路由定义中的loader方法获取到的内容,在组件中需要通过useLoaderData() Hook 来获取。loader方法接受一个对象作为参数。这个对象中的字段有以下两个。

  • params,React Router 中定义路由路径中出现的路径参数。
  • request,应用中发出的请求对象,其中可以通过以下几个常用字段来访问业务相关的功能。
    • url,使用URL类解析后可以从中获取查询串(URLSearchParams)。

loader方法中除了可以直接返回数据以外,还可以直接返回一个 Web 响应(Response)。例如可以将fetch的结果直接返回。这样在组件中使用useLoaderData() Hook 获取到的将是响应中响应体携带的数据。

Tip

如果在loader方法中抛出一个携带着异常HTTP状态码的响应,那么React Router将会导航到errorElement指定的组件中。

自定义shouldRevalidate

在以下几个默认的情况下,loader方法都会被自动调用来刷新组件中的数据。

  • 当使用<form>提交后调用了action方法。这种情况包括采用任何形式的表单提交行为。
  • 当 URL 中的参数发生了变化。包括 URL 中的路径参数和查询参数发生变化的情况。
  • 当重新路由到当前路由的时候。

但是如果想要增加手动控制loader方法重新运行,就需要在路由定义中定义shouldRevalidate方法。当这个方法返回true的时候,React Router 就会重新指定loader方法来刷新组件中的数据。shouldRevalidate方法同样也是只接受一个对象作为参数,其中常用的字段有以下这些。

  • currentUrl,当前页面的 URL。
  • currentParams,当前页面中的路径参数。
  • nextUrl,即将导航向的 URL。
  • nextParams,即将导航向的页面中的路径参数。
  • formMethod,表单提交方法。
  • formAction,表单提交动作。
  • formEncType,表单数据封装方法。
  • formData,表单提交的数据。
  • actionResultaction方法处理的结果。
  • defaultShouldRevalidate,React Router 中默认shouldRevalidate行为的判断结果。

redirect

React Router 中还提供了一个redirect工具函数,可以用来在loader方法和action方法中进行导航转向。使用起来也是非常的简单。如下例所示。

const loader = async () => {
  const data = await getData();

  if (isEmpty(data)) {
    return reditect("/not-found-data");
  }

  return defer({
    data,
  });
};

redirect函数实际上构建了一个 HTTP 状态码为 302 的响应,在loader方法中返回一个 HTTP 响应实际上会导致 React Router 堆响应的解析,从而就实现了导航转向。实际上 React Router 也推荐在loader方法和action方法中使用redirect来代替使用useNavigate()来进行导航转向。

Tip

除了redirect函数以外,React Router还提供了一个json函数,可以用来构建一个响应体内容为JSON数据的响应。

action方法

Caution

action方法目前仅适用于Browser Router。

action方法是与loader方法相对的,根据 React Router 文档中的描述,loader方法是向组件中读入数据的方法,而action方法则是一种从组件中写出数据的方法。action方法所能够接受的参数与loader方法是一致的。

action方法中返回的数据可以在组件中使用useActionData() Hook 来获取。

Note

因为标记为index的路由是没有其实际路径的,如果想要将表单提交到标记为index的路由组件的action方法上,那么就需要将表单的提交目标action属性设置为?index的查询形式,例如/projects?index

errorElement

在 React 引入函数组件以后,如何处理函数组件中抛出的错误就不像类组件中那样方便定义了,所以在应用开发过程中经常会被 React 报出缺少错误边界的警告。

React Router 中路由定义中的errorElement就提供了这样一个错误边界的功能。React Router 会在loader方法、action方法、组件渲染失败等情况下转而去渲染errorElement定义的组件,并且可以在errorElememt组件中使用useRouteError() Hook 来获取组件和各种方法中抛出的错误。

Tip

想要手动抛出错误可以直接使用throw,抛出的错误会被errorElement指定的组件捕获。

在实际应用开发中,推荐在根路由中定义一个可以适用于全局的errorElement,以保证整个路由系统中抛出的错误都能够被有效的捕获。