一文学会使用React-Router v6

文章目录

    • 基本格式
      • createBrowserRouter
        • Type Declaration
          • routes
          • basename
          • future and window ... 省略
        • fall上述文本提到 `createBrowserRouter` 函数的一个特性:在非服务器端渲染应用程序时,当它挂载时会初始化所有匹配的路由加载器。在此期间,你可以提供 `fallbackElement`,以向用户显示应用程序正在加载的指示。
        • future ...
      • Components

以下内容来自官方文档 https://reactrouter.com/en/main

基本格式

import * as React from "react";
import { createRoot } from "react-dom/client";
import {
  createBrowserRouter,
  RouterProvider,
  Route,
  Link,
} from "react-router-dom";

const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <div>
        <h1>Hello World</h1>
        <Link to="about">About Us</Link>
      </div>
    ),
  },
  {
    path: "about",
    element: <div>About</div>,
  },
]);

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

createBrowserRouter

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} />
);

Type Declaration
function createBrowserRouter(
  routes: RouteObject[],
  opts?: {
    basename?: string;
    future?: FutureConfig;
    hydrationData?: HydrationState;
    window?: Window;
  }
): RemixRouter;

routes

An array of Route objects with nested routes on the children property.

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

Route定义为:

const router = createBrowserRouter([
  {
    // it renders this element
    element: <Team />,

    // when the URL matches this segment
    path: "teams/:teamId",

    // with this data loaded before rendering
    loader: async ({ request, params }) => {
      return fetch(
        `/fake/api/teams/${params.teamId}.json`,
        { signal: request.signal }
      );
    },

    // performing this mutation when data is submitted to it
    action: async ({ request }) => {
      return updateFakeTeam(await request.formData());
    },

    // and renders this element in case something went wrong
    errorElement: <ErrorBoundary />,
  },
]);

RouteObject对象的声明为:

interface RouteObject {
  path?: string;
  index?: boolean;
  children?: React.ReactNode;
  caseSensitive?: boolean;
  id?: string;
  loader?: LoaderFunction;
  action?: ActionFunction;
  element?: React.ReactNode | null;
  Component?: React.ComponentType | null;
  errorElement?: React.ReactNode | null;
  ErrorBoundary?: React.ComponentType | null;
  handle?: RouteObject["handle"];
  shouldRevalidate?: ShouldRevalidateFunction;
  lazy?: LazyRouteFunction<RouteObject>;
}

对于Path:
动态路由参数, 可以在loader, action中通过params直接点出 或者使用useParams()钩子来获取该参数

<Route
  // this path will match URLs like
  // - /teams/hotspur
  // - /teams/real
  path="/teams/:teamId"
  // the matching param will be available to the loader
  loader={({ params }) => {
    console.log(params.teamId); // "hotspur"
  }}
  // and the action
  action={({ params }) => {}}
  element={<Team />}
/>;

// and the element through `useParams`
function Team() {
  let params = useParams();
  console.log(params.teamId); // "hotspur"
}

你可以定义多个动态参数 都可以通过 . 运算符获取到

<Route path="/c/:categoryId/p/:productId" />;
// both will be available
params.categoryId;
params.productId;

可选参数 通过在后面加上 ?

<Route
  // this path will match URLs like
  // - /categories
  // - /en/categories
  // - /fr/categories
  path="/:lang?/categories"
  // the matching param might be available to the loader
  loader={({ params }) => {
    console.log(params["lang"]); // "en"
  }}
  // and the action
  action={({ params }) => {}}
  element={<Categories />}
/>;

// and the element through `useParams`
function Categories() {
  let params = useParams();
  console.log(params.lang);
}

当然可以同时用动态参数和可选参数

模糊匹配所有的 * 参数

<Route
  // this path will match URLs like
  // - /files
  // - /files/one
  // - /files/one/two
  // - /files/one/two/three
  path="/files/*"
  // the matching param will be available to the loader
  loader={({ params }) => {
    console.log(params["*"]); // "one/two"
  }}
  // and the action
  action={({ params }) => {}}
  element={<Team />}
/>;

// and the element through `useParams`
function Team() {
  let params = useParams();
  console.log(params["*"]); // "one/two"
}

布局路由

<Route
  element={
    <div>
      <h1>Layout</h1>
      <Outlet />
    </div>
  }
>
  <Route path="/" element={<h2>Home</h2>} />
  <Route path="/about" element={<h2>About</h2>} />
</Route>

组件留出子路由要渲染的位置,仅此而已
index索引路由 url为父路径时显示的组件

}>
  } />
  } />

  • 外层的有一个路径path为"/teams",并渲染了一个组件
  • 在这个路由内部,有另一个带有index属性的嵌套的。这意味着当路径匹配"/teams"时,组件将被呈现到其父级路由的Outlet中(在这种情况下是)。
  • 此外,还有另一个带有path=":teamId"的嵌套路由,表示当路径匹配"/teams/:teamId"时,将呈现组件

children: 嵌套路由
caseSensitive: 表示对路径中大小写敏感
Instructs the route to match case or not:

<Route caseSensitive path="/wEll-aCtuA11y" />
  • Will match “wEll-aCtuA11y”
  • Will not match “well-actua11y”

loader
每一个路由都能定义一个loader函数来为路由组件渲染之前提供数据

createBrowserRouter([
  {
    element: <Teams />,
    path: "teams",
    loader: async () => {
      return fakeDb.from("teams").select("*");
    },
    children: [
      {
        element: <Team />,
        path: ":teamId",
        loader: async ({ params }) => {
          return fetch(`/api/teams/${params.teamId}.json`);
        },
      },
    ],
  },
]);

通过 调用loader函数获得的数据可以通过 useLoaderData钩子拿到
useLoaderData: This hook provides the value returned from your route loader.

import {
  createBrowserRouter,
  RouterProvider,
  useLoaderData,
} from "react-router-dom";

function loader() {
  return fetchFakeAlbums();
}

export function Albums() {
  const albums = useLoaderData();
  // ...
}

const router = createBrowserRouter([
  {
    path: "/",
    loader: loader,
    element: <Albums />,
  },
]);

ReactDOM.createRoot(el).render(
  <RouterProvider router={router} />
);

在调用路由操作之后,数据将自动重新验证,并从加载器返回最新的结果。
请注意,useLoaderData 不会启动抓取。它只是读取 React Router 在内部管理的抓取结果,因此您不必担心在重新呈现的原因之外的情况下重新获取数据。
这也意味着在重新呈现之间返回的数据是稳定的,因此您可以安全地将其传递给 React hooks(如 useEffect)中的依赖数组。它只在再次调用加载器后(在执行操作或特定导航后)才会更改。在这些情况下,标识将更改(即使值没有更改)。
您可以在任何组件或任何自定义 hook 中使用此钩子,而不仅仅是在 Route 元素中。它将返回上下文中最近路由的数据。
params: 由url中解析的动态参数会放在params中并传递给loader函数

createBrowserRouter([
  {
    path: "/teams/:teamId",
    loader: ({ params }) => {
      return fakeGetTeam(params.teamId);
    },
  },
]);

request:

<a
  href={props.to}
  onClick={(event) => {
    event.preventDefault();
    navigate(props.to);
  }}
/>

React router阻止浏览器将请求request发送给server, 而是转给了loader函数

function loader({ request }) {
  const url = new URL(request.url);
  const searchTerm = url.searchParams.get("q");
  return searchProducts(searchTerm);
}

返回 response
可以返回fetch()发送异步请求(ajax, axios)的结果,也可以自定义返回结果

// an HTTP/REST API
function loader({ request }) {
  return fetch("/api/teams.json", {
    signal: request.signal,
  });
}

// or even a graphql endpoint
function loader({ request, params }) {
  return fetch("/_gql", {
    signal: request.signal,
    method: "post",
    body: JSON.stringify({
      query: gql`...`,
      params: params,
    }),
  });
}

function loader({ request, params }) {
  const data = { some: "thing" };
  return new Response(JSON.stringify(data), {
    status: 200,
    headers: {
      "Content-Type": "application/json; utf-8",
    },
  });
}

react router会自动调用 response.json()

function SomeRoute() {
  const data = useLoaderData();
  // { some: "thing" }
}

在loader中抛出异常

function loader({ request, params }) {
  const res = await fetch(`/api/properties/${params.id}`);
  if (res.status === 404) {
    throw new Response("Not Found", { status: 404 });
  }
  return res.json();
}

会渲染路由中定义的 errorElement组件
action: 进行写操作的函数

<Route
  path="/song/:songId/edit"
  element={<EditSong />}
  action={async ({ params, request }) => {
    let formData = await request.formData();
    return fakeUpdateSong(params.songId, formData);
  }}
  loader={({ params }) => {
    return fakeGetSong(params.songId);
  }}
/>

在应用发起一个非get请求时被调用 比如按钮点击

// forms
<Form method="post" action="/songs" />;
<fetcher.Form method="put" action="/songs/123/edit" />;

// imperative submissions
let submit = useSubmit();
submit(data, {
  method: "delete",
  action: "/songs/123",
});
fetcher.submit(data, {
  method: "patch",
  action: "/songs/123/edit",
});

element / Component: 要渲染的组件
errorElement / ErrorBoundary: 异常时渲染的组件
当在loader, action函数执行或者组件渲染时抛出异常, 就不会渲染element组件, 而是用error path的组件 errorElement渲染, 该异常可以用 useRouteError获取
lazy :

let routes = createRoutesFromElements(
  <Route path="/" element={<Layout />}>
    <Route path="a" lazy={() => import("./a")} />
    <Route path="b" lazy={() => import("./b")} />
  </Route>
);

basename

The basename of the app for situations where you can’t deploy to the root of the domain, but a sub directory.

createBrowserRouter(routes, {
  basename: "/app",
});

future and window … 省略

All data router objects are passed to this component to render your app and enable the rest of the data APIs

import {
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";

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

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

fall上述文本提到 createBrowserRouter 函数的一个特性:在非服务器端渲染应用程序时,当它挂载时会初始化所有匹配的路由加载器。在此期间,你可以提供 fallbackElement,以向用户显示应用程序正在加载的指示。

这个特性的目的是在加载路由数据时,提供一个加载过程中的占位元素(fallback element),使用户在等待加载完成时能够看到一些界面反馈,而不是一片空白。
举例来说,你可以在 createBrowserRouter 中添加 fallbackElement 选项,像这样:

createBrowserRouter(routes, {
  fallbackElement: <div>Loading...</div>,
});

这样,当路由加载的过程中,页面将显示 “Loading…”,以告诉用户应用程序正在处理数据,避免用户在加载过程中感到不确定或无反馈。

future …

Components

import { Await, useLoaderData } from "react-router-dom";

function Book() {
  const { book, reviews } = useLoaderData();
  return (
    <div>
      <h1>{book.title}</h1>
      <p>{book.description}</p>
      <React.Suspense fallback={<ReviewsSkeleton />}>
        <Await
          resolve={reviews}
          errorElement={
            <div>Could not load reviews </div>
          }
          children={(resolvedReviews) => (
            <Reviews items={resolvedReviews} />
          )}
        />
      </React.Suspense>
    </div>
  );
}

这段代码是一个使用 React Router 的 useLoaderDataAwait 组件的 React 组件。让我解释一下它的主要部分:

  1. useLoaderData 是 React Router 提供的一个 hook,用于获取路由加载器(loader)返回的数据。在这里,通过 useLoaderData 获取了 bookreviews
  2. return 中,首先渲染了 book 的标题和描述。
  3. 使用了 React 的 Suspense 组件,它接受 fallback 属性,指定在子组件加载过程中显示的占位元素。在这里,如果 reviews 尚未加载完成,将显示一个 ReviewsSkeleton 组件,用作加载占位符。
  4. 使用 Await 组件包装了 reviews 的加载。Await 组件接受 resolve 属性,指定需要等待的 Promise。如果 resolve Promise 处于 pending 状态,它会渲染 fallback 中的占位元素;如果 Promise 处于 resolved 状态,它会调用 children 函数,并将 resolved 的数据传递给它。
  5. children 函数中,渲染了一个 Reviews 组件,传递了从 reviews 获取的 resolved 数据。

总体来说,这段代码的目的是展示书籍的标题和描述,同时以异步方式加载书籍的评论。在加载评论的过程中,会显示一个加载中的占位符,确保用户在等待数据加载完成时能够看到一些界面反馈。如果加载评论失败,将显示一个错误提示。

你可能感兴趣的:(React,react.js,javascript,前端)