HOW - React Router v6.x Feature 实践(react-router-dom)

目录

  • 基本特性
  • [ranked routes matching](#ranked routes matching)
  • [active links](#active links)
  • [relative links](#relative links)
      • [1. 相对路径的使用](#1. 相对路径的使用)
      • [2. 嵌套路由的增强行为](#2. 嵌套路由的增强行为)
      • [3. 优势和注意事项](#3. 优势和注意事项)
      • [4. . 和 ..](#4. . 和 ..)
      • [5. 总结](#5. 总结)
  • [data loading](#data loading)
  • [loading or changing data and redirect](#loading or changing data and redirect)
  • [pending navigation ui](#pending navigation ui)
  • [skeleton ui with suspense](#skeleton ui with suspense)
  • [data mutations with `<Route action>`](#data mutations with <Route action>)
  • [busy indicators with route actions](#busy indicators with route actions)
  • [data fetchers](#data fetchers)

基本特性

  1. client side routing
  2. nested routes
  3. dynamic segments

比较好理解,这里不赘述。

ranked routes matching

https://reactrouter.com/en/main/start/overview#ranked-route-matching

When matching URLs to routes, React Router will rank the routes according to the number of segments , static segments , dynamic segments , splats, etc. and pick the most specific match.

这句话描述了 React Router 在匹配 URL 和路由时的策略,即根据路由的具体性来优先选择最合适的匹配项。让我们逐步解析这句话的含义:

  1. URL 和路由的匹配

    • 当用户访问某个 URL 时,React Router 需要确定哪个路由规则最适合处理该 URL。例如,对于 URL /users/123,React Router 需要决定是匹配 /users/:id 还是其他定义的路由。
  2. 路由匹配的考量因素:优先级由高到低

    • 路由的段数(Segments) :URL 和路由可以分成多个段(segments),例如 /users/123 有两个段,/users/:id 也有两个段。React Router 会比较 URL 和每个路由的段数,越多的段数一般意味着路由更具体。

    • 静态段(Static Segments) :静态段是指在路由中直接指定的固定路径,例如 /users 是一个静态段。React Router 会考虑静态段的数量来确定路由的具体性。

    • 动态段(Dynamic Segments) :动态段是指在路由中使用参数化路径,例如 /users/:id 中的 :id 是一个动态段。动态段的存在可能使得路由更灵活但也更具体。

    • 通配符(Splat) :通配符(如 *)表示匹配多个路径段,通常用于处理不确定数量的路径部分。

  3. 最具体的匹配

    • React Router 会通过比较以上因素来确定哪个路由定义是最具体的匹配。具体的路由定义意味着它能够最准确地匹配当前的 URL,而不会与其他可能的路由定义冲突。
  4. 示例

tsx 复制代码
<Route path="/teams/:teamId" />
<Route path="/teams/new" />

对于 http://example.com/teams/new. 会优先匹配第二个 Route。因为静态段数为 2,更具体。

理解 React Router 的路由匹配策略,特别是根据路由的具体性来优先选择最合适的匹配项,有助于开发者更有效地设计和管理复杂的路由结构。通过正确的路由定义和优先级排序,可以确保应用程序在导航和页面渲染时行为符合预期,并能够灵活地应对各种场景和URL路径。

https://reactrouter.com/en/main/components/nav-link

tsx 复制代码
<NavLink
  style={({ isActive, isPending }) => {
    return {
      color: isActive ? "red" : "inherit",
    };
  }}
  className={({ isActive, isPending }) => {
    return isActive ? "active" : isPending ? "pending" : "";
  }}
/>

useMatch

https://reactrouter.com/en/main/hooks/use-match

tsx 复制代码
function SomeComp() {
  const match = useMatch("/messages");
  return <li className={Boolean(match) ? "active" : ""} />;
}

理解 React Router 中 <Link><NavLink> 组件相对路径的使用需要考虑它们与 HTML 中 <a> 标签的行为差异,尤其是在嵌套路由场景下的增强行为。

1. 相对路径的使用

  • HTML <a> 标签 :在 HTML 中,使用 <a> 标签时,相对路径通常相对于当前页面的完整 URL。这意味着,相对路径会根据当前页面的路径来构建最终的目标 URL。

    html 复制代码
    <a href="about">About</a>
    • 如果当前 URL 是 http://example.com/home,那么点击上述链接将导航到 http://example.com/about
  • React Router 中的 <Link><NavLink> :在 React Router 中,<Link><NavLink> 组件可以接受相对路径,但它们的行为略有不同。

    jsx 复制代码
    import { Link, NavLink } from 'react-router-dom';
    
    <Link to="about">About</Link>
    <NavLink to="about">About</NavLink>
    • 这里的 to="about" 是相对路径,相对于当前路由的路径来构建目标 URL。例如,如果当前路由是 /home,那么这两个链接将会导航到 /home/about

2. 嵌套路由的增强行为

  • 嵌套路由 :当应用程序中存在嵌套路由时,React Router 的 <Link><NavLink> 组件表现出更智能的行为,确保相对路径的正确解析。

    jsx 复制代码
    <Route path="/home">
      <Link to="about">About</Link>
      <NavLink to="about">About</NavLink>
    </Route>
    • 在上述例子中,假设当前路由是 /home,那么 <Link><NavLink> 组件会基于当前路由的路径 /home 构建相对路径,导航到 /home/about

3. 优势和注意事项

  • 灵活性和便利性:相对路径的使用使得在应用中链接管理更加灵活和简单,尤其是在处理嵌套路由时。

  • 注意路径解析 :确保理解相对路径在不同嵌套层级下的解析规则。React Router 的行为通常是基于当前活动的路由 来解析相对路径,而不是简单地相对于根路径

4. . 和 ...

jsx 复制代码
<Route path="/home">
  <Link to=".">About</Link>
  <NavLink to=".">About</NavLink>
</Route>
  • 在上述例子中,假设当前路由是 /home,那么 <Link><NavLink> 组件会基于当前路由的路径 /home 构建相对路径,导航到 /home
jsx 复制代码
<Route path="home" element={<Home />}>
  <Route path="project/:projectId" element={<Project />}>
  	<Route path=":taskId" element={<Task />} />
  </Route>
</Route>

Project 中会渲染:

jsx 复制代码
  <Link to="abc">
  <Link to=".">
  <Link to=".."></Link>
  <Link to=".." relative="path">
  • 在上述例子中,假设当前路由是 /home/project/123,那么 <Link> 会基于当前路由的路径构建相对路径,分别导航到 /home/project/123/abc/home/project/abc/home/home/project

注意后面两个的差异:

By default, the ... in relative links traverse the route hierarchy, not the URL segments. Adding relative="path" in the next example allows you to traverse the path segments instead.

5. 总结

理解 React Router 中 <Link><NavLink> 组件相对路径的行为,特别是在嵌套路由情况下的增强行为,有助于开发者更有效地管理和导航应用程序中的链接。相对路径的使用使得在不同层级和场景下的导航操作更加灵活和便捷,但需要注意理解和控制路径的解析和构建规则。

data loading

https://reactrouter.com/en/main/start/overview#data-loading

Combined with nested routes, all of the data for multiple layouts at a specific URL can be loaded in parallel.

jsx 复制代码
<Route
  path="/"
  loader={async ({ request }) => {
    // loaders can be async functions
    const res = await fetch("/api/user.json", {
      signal: request.signal,
    });
    const user = await res.json();
    return user;
  }}
  element={<Root />}
>
  <Route
    path=":teamId"
    // loaders understand Fetch Responses and will automatically
    // unwrap the res.json(), so you can simply return a fetch
    loader={({ params }) => {
      return fetch(`/api/teams/${params.teamId}`);
    }}
    element={<Team />}
  >
    <Route
      path=":gameId"
      loader={({ params }) => {
        // of course you can use any data store
        return fakeSdk.getTeam(params.gameId);
      }}
      element={<Game />}
    />
  </Route>
</Route>

Data is made available to your components through useLoaderData.

jsx 复制代码
function Root() {
  const user = useLoaderData();
  // data from <Route path="/">
}

function Team() {
  const team = useLoaderData();
  // data from <Route path=":teamId">
}

function Game() {
  const game = useLoaderData();
  // data from <Route path=":gameId">
}

When the user visits or clicks links to https://example.com/real-salt-lake/45face3, all three route loaders will be called and loaded in parallel, before the UI for that URL renders.

loading or changing data and redirect

https://reactrouter.com/en/main/route/loader#throwing-in-loaders

jsx 复制代码
<Route
  path="dashboard"
  loader={async () => {
    const user = await fake.getUser();
    if (!user) {
      // if you know you can't render the route, you can
      // throw a redirect to stop executing code here,
      // sending the user to a new route
      throw redirect("/login");
    }

    // otherwise continue
    const stats = await fake.getDashboardStats();
    return { user, stats };
  }}
/>

https://reactrouter.com/en/main/start/overview#pending-navigation-ui

When users navigate around the app, the data for the next page is loaded before the page is rendered. It's important to provide user feedback during this time so the app doesn't feel like it's unresponsive.

jsx 复制代码
function Root() {
  const navigation = useNavigation();
  return (
    <div>
      {navigation.state === "loading" && <GlobalSpinner />}
      <FakeSidebar />
      <Outlet />
      <FakeFooter />
    </div>
  );
}

skeleton ui with suspense

https://reactrouter.com/en/main/start/overview#skeleton-ui-with-suspense

Instead of waiting for the data for the next page, you can defer data so the UI flips over to the next screen with placeholder UI immediately while the data loads.

defer enables suspense for the un-awaited promises

jsx 复制代码
<Route
  path="issue/:issueId"
  element={<Issue />}
  loader={async ({ params }) => {
    // these are promises, but *not* awaited
    const comments = fake.getIssueComments(params.issueId);
    const history = fake.getIssueHistory(params.issueId);
    // the issue, however, *is* awaited
    const issue = await fake.getIssue(params.issueId);

    // defer enables suspense for the un-awaited promises
    return defer({ issue, comments, history });
  }}
/>;

function Issue() {
  const { issue, history, comments } = useLoaderData();
  return (
    <div>
      <IssueDescription issue={issue} />

      {/* Suspense provides the placeholder fallback */}
      <Suspense fallback={<IssueHistorySkeleton />}>
        {/* Await manages the deferred data (promise) */}
        <Await resolve={history}>
          {/* this calls back when the data is resolved */}
          {(resolvedHistory) => (
            <IssueHistory history={resolvedHistory} />
          )}
        </Await>
      </Suspense>

      <Suspense fallback={<IssueCommentsSkeleton />}>
        <Await resolve={comments}>
          {/* ... or you can use hooks to access the data */}
          <IssueComments />
        </Await>
      </Suspense>
    </div>
  );
}

function IssueComments() {
  const comments = useAsyncValue();
  return <div>{/* ... */}</div>;
}

涉及如下 API 结合使用:

  1. defer
  2. Await
  3. useAsyncValue

data mutations with <Route action>

https://reactrouter.com/en/main/start/overview#data-mutations

HTML forms are navigation events, just like links. React Router supports HTML form workflows with client side routing.

When a form is submitted, the normal browser navigation event is prevented and a Request, with a body containing the FormData of the submission, is created. This request is sent to the <Route action> that matches the form's <Form action>.

Form elements's name prop are submitted to the action:

jsx 复制代码
<Form action="/project/new">
  <label>
    Project title
    <br />
    <input type="text" name="title" />
  </label>

  <label>
    Target Finish Date
    <br />
    <input type="date" name="due" />
  </label>
</Form>
<Route
  path="project/new"
  action={async ({ request }) => {
    const formData = await request.formData();
    const newProject = await createProject({
      title: formData.get("title"),
      due: formData.get("due"),
    });
    return redirect(`/projects/${newProject.id}`);
  }}
/>

在 HTML 中,<form> 元素的 action 属性定义了当用户提交表单时将数据发送到的服务器端的 URL。

具体来说:

  • action 属性的作用

    • 当用户提交表单时,浏览器会将表单中的数据发送到指定的 URL。
    • 这个 URL 可以是相对路径或绝对路径。
    • 如果 action 属性未指定,表单会被提交到当前页面的 URL(即自身)。
  • 使用示例

    html 复制代码
    <form action="/project/new" method="post">
        <!-- 表单内容 -->
        <input type="text" name="project_name" />
        <button type="submit">提交</button>
    </form>
    • 在这个例子中,action 属性的值是 "/project/new"。当用户点击提交按钮时,表单数据将被发送到当前服务器的 /project/new 路径。
  • 重要说明

    • 如果 action 属性指向一个相对路径,表单数据会被提交到当前页面的基础 URL 加上 action 的值。
    • 如果 action 属性是绝对路径(例如 http://example.com/project/new),数据将被发送到指定的绝对路径。
  • HTTP 方法 (method 属性)

    • 另一个与 action 相关的重要属性是 method,它指定了使用何种 HTTP 方法将表单数据发送到服务器。
    • 常见的方法是 GETPOSTGET 方法将数据附加到 URL 上(可见),而 POST 方法将数据包含在请求体中(不可见)。

总结来说,action 属性定义了表单数据提交的目标 URL。这对于将用户输入数据发送到后端处理或其他指定的处理程序非常重要。

busy indicators with route actions

https://reactrouter.com/en/main/start/overview#busy-indicators

When forms are being submitted to route actions, you have access to the navigation state to display busy indicators, disable fieldsets, etc.

jsx 复制代码
function NewProjectForm() {
  const navigation = useNavigation();
  const busy = navigation.state === "submitting";
  return (
    <Form action="/project/new">
      <fieldset disabled={busy}>
        <label>
          Project title
          <br />
          <input type="text" name="title" />
        </label>

        <label>
          Target Finish Date
          <br />
          <input type="date" name="due" />
        </label>
      </fieldset>
      <button type="submit" disabled={busy}>
        {busy ? "Creating..." : "Create"}
      </button>
    </Form>
  );
}

data fetchers

HTML Forms are the model for mutations but they have one major limitation: you can have only one at a time because a form submission is a navigation.

Most web apps need to allow for multiple mutations to be happening at the same time, like a list of records where each can be independently deleted, marked complete, liked, etc.

Fetchers allow you to interact with the route actions and loaders without causing a navigation in the browser, but still getting all the conventional benefits like error handling , revalidation , interruption handling , and race condition handling.

Imagine a list of tasks:

tsx 复制代码
function Tasks() {
  const tasks = useLoaderData();
  return tasks.map((task) => (
    <div>
      <p>{task.name}</p>
      <ToggleCompleteButton task={task} />
    </div>
  ));
}

Each task can be marked complete independently of the rest, with its own pending state and without causing a navigation with a fetcher:

jsx 复制代码
function ToggleCompleteButton({ task }) {
  const fetcher = useFetcher();

  return (
    <fetcher.Form method="post" action="/toggle-complete">
      <fieldset disabled={fetcher.state !== "idle"}>
        <input type="hidden" name="id" value={task.id} />
        <input
          type="hidden"
          name="status"
          value={task.complete ? "incomplete" : "complete"}
        />
        <button type="submit">
          {task.status === "complete"
            ? "Mark Incomplete"
            : "Mark Complete"}
        </button>
      </fieldset>
    </fetcher.Form>
  );
}
相关推荐
Myli_ing27 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维44 分钟前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
雯0609~1 小时前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ1 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
彭世瑜2 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4042 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish2 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five2 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序2 小时前
vue3 封装request请求
java·前端·typescript·vue