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

基本特性

  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. 示例

<Route path="/teams/:teamId" />
<Route path="/teams/new" />

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

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

active links

NavLink

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

<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

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

relative links

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

1. 相对路径的使用

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

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

    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> 组件表现出更智能的行为,确保相对路径的正确解析。

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

3. 优势和注意事项

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

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

4. . 和 …

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

Project 中会渲染:

  <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.

<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.

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

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

pending navigation ui

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.

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

<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:

<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(即自身)。
  • 使用示例

    <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.

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:

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:

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>
  );
}
  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
react-router-domReact框架中用于处理路由的库,它提供了一种方便的方式来管理应用程序的不同页面之间的导航和状态。而import.meta.glob是ES模块的一个新特性,它可以用于动态地导入模块。 在react-router-dom 6中,import.meta.glob可以用于动态地导入路由组件。它可以根据指定的模式匹配文件路径,并将匹配到的文件作为路由组件进行导入。这样可以方便地实现按需加载路由组件,提高应用程序的性能和加载速度。 具体使用方法如下: 1. 首先,在项目中安装react-router-dom 6: ``` npm install react-router-dom@next ``` 2. 在需要使用import.meta.glob的地方,使用如下语法进行导入: ```jsx import { lazy } from 'react'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; const routes = import.meta.glob('/path/to/routes/*.jsx'); function App() { return ( <Router> <Routes> {Object.entries(routes).map(([path, component]) => { const routePath = path.replace('/path/to/routes', '').replace('.jsx', ''); const LazyComponent = lazy(component); return <Route key={routePath} path={routePath} element={<LazyComponent />} />; })} </Routes> </Router> ); } export default App; ``` 在上面的代码中,`import.meta.glob('/path/to/routes/*.jsx')`会根据指定的模式匹配`/path/to/routes/`目录下的所有`.jsx`文件,并返回一个对象,其中键是文件路径,值是对应的模块。 然后,我们使用`Object.entries(routes)`将对象转换为数组,并使用`map`方法遍历数组,生成对应的`Route`组件。在遍历过程中,我们使用`lazy`函数将路由组件进行懒加载,以实现按需加载的效果。 这样,我们就可以根据文件路径动态地导入路由组件,并在应用程序中进行路由配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值