react router6.x官方DEMO

Tutorial v6.4.2 | React Router

初始化项目

import React from "react";
import ReactDOM from "react-dom/client";
import {
  createBrowserRouter,
  RouterProvider,
  Route,
} from "react-router-dom";
import "./index.css";


const router = createBrowserRouter([
  {
    path: "/",
    element: <div>Hello world!</div>,
  },
]);

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

准备额外文件

src目录下需要创建和编辑index.csscontacts.js文件

css

js

创建DEMO主界面并修改路由

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

routes/root.tsx 定义Root组件

export default function Root() {
    return (
      <>
        

现在看起来

react router6.x官方DEMO_第1张图片

添加error界面

函数时编程,useRouteError函数获取出错信息

import { useRouteError } from "react-router-dom";

export default function ErrorPage() {
  const error = useRouteError();
  console.error(error);

  return (
    <div id="error-page">
      <h1>Oops!</h1>
      <p>Sorry, an unexpected error has occurred.</p>
      <p>
        <i>{error.statusText || error.message}</i>
      </p>
    </div>
  );
}

注册error界面

main.tsx

import ErrorPage from "./error-page";


const router = createBrowserRouter([
  {
    path: "/",
    element: ,
    errorElement: ,
  },
]);

当路由出错时:

react router6.x官方DEMO_第2张图片

Note that useRouteError provides the error that was thrown. When the user navigates to routes that don’t exist you’ll get an error response with a “Not Found” statusText. We’ll see some other errors later in the tutorial and discuss them more.

For now, it’s enough to know that pretty much all of your errors will now be handled by this page instead of infinite spinners, unresponsive pages, or blank screens

联系人组件

声明组件routes/contact.tsx

import { Form } from "react-router-dom";

export default function Contact() {
  const contact = {
    first: "Your",
    last: "Name",
    avatar: "https://placekitten.com/g/200/200",
    twitter: "your_handle",
    notes: "Some notes",
    favorite: true,
  };

  return (
    <div id="contact">
      <div>
        <img
          key={contact.avatar}
          src={contact.avatar || null}
        />
      </div>

      <div>
        <h1>
          {contact.first || contact.last ? (
            <>
              {contact.first} {contact.last}
            </>
          ) : (
            <i>No Name</i>
          )}{" "}
          <Favorite contact={contact} />
        </h1>

        {contact.twitter && (
          <p>
            <a
              target="_blank"
              href={`https://twitter.com/${contact.twitter}`}
            >
              {contact.twitter}
            </a>
          </p>
        )}

        {contact.notes && <p>{contact.notes}</p>}

        <div>
          <Form action="edit">
            <button type="submit">Edit</button>
          </Form>
          <Form
            method="post"
            action="destroy"
            onSubmit={(event) => {
              if (
                !confirm(
                  "Please confirm you want to delete this record."
                )
              ) {
                event.preventDefault();
              }
            }}
          >
            <button type="submit">Delete</button>
          </Form>
        </div>
      </div>
    </div>
  );
}

function Favorite({ contact }) {
  // yes, this is a `let` for later
  let favorite = contact.favorite;
  return (
    <Form method="post">
      <button
        name="favorite"
        value={favorite ? "false" : "true"}
        aria-label={
          favorite
            ? "Remove from favorites"
            : "Add to favorites"
        }
      >
        {favorite ? "★" : "☆"}
      </button>
    </Form>
  );
}

注册路由组件

import Contact from "./routes/contact";

const router = createBrowserRouter([
  {
    path: "/",
    element: ,
    errorElement: ,
  },
  {
    path: "contacts/:contactId",
    element: ,
  },
]);

现在contacts会单独在一个页面中显示,我们希望他在Root组件右侧显示,即搜索后,在右侧显示联系人信息。

嵌套路由Nested Routes

const router = createBrowserRouter([
  {
    path: "/",
    element: ,
    errorElement: ,
    children: [
      {
        path: "contacts/:contactId",
        element: ,
      },
    ],
  },
]);

原本的Root组件结构

react router6.x官方DEMO_第3张图片

我们的目的是在Root组件里面显示下级路由组件Contacts的内容,官方是这样做的:

import { Outlet } from "react-router-dom";

在一个空div中,添加了Outlet组件,那么我们可以猜到,子路由的组件将渲染到该位置。

效果:

react router6.x官方DEMO_第4张图片

Outlet组件的官方说明

Outlet v6.4.2 | React Router

An should be used in parent route elements to render their child route elements. This allows nested UI to show up when child routes are rendered. If the parent route matched exactly, it will render a child index route or nothing if there is no index route.

在父路由元素中应该使用来呈现子路由元素。这允许在呈现子路由时显示嵌套UI。如果父路由完全匹配,它将呈现子索引路由,如果没有索引路由则不呈现子索引路由。

修改左侧跳转标签

将标签替换为组件

 
  • Your Name
  • Your Friend

根据id加载数据

URL段、布局、数据经常耦合在一起,例如:

URL Segment Component Data
/ list of contacts
contacts/:id individual contact

因此,React Router定义了一些约定(data conventions) 帮助将数据传给路由组件,包括loaderuseLoaderData.

root.tsx中导出一个loader函数:

import { getContacts } from "../contacts";

export async function loader() {
  const contacts = await getContacts();
  return { contacts };
}

loader

loader v6.4.2 | React Router

Each route can define a “loader” function to provide data to the route element before it renders.

在路由组件渲染前(挂载)给组件传递数据。

路由组件获取数据并显示

root.tsx

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

/* other code */

export default function Root() {
  const { contacts } = useLoaderData();
  return (
    <>
      
    
  );
}

效果

react router6.x官方DEMO_第5张图片

由于getContacts没有接收到参数,所以返回的是空对象。显示无联系人

Data Writes + HTML Forms

Root组件中的New按钮触发了表单提交事件

								

Tutorial v6.4.2 | React Router

While unfamiliar to some web developers, HTML forms actually cause a navigation in the browser, just like clicking a link. The only difference is in the request: links can only change the URL while forms can also change the request method (GET vs POST) and the request body (POST form data).

虽然对一些web开发人员来说并不熟悉,但HTML表单实际上会在浏览器中产生导航,就像点击链接一样。唯一的区别在于请求:链接只能更改URL,而表单还可以更改请求方法(GET vs POST)和请求体(POST表单数据)。

Instead of sending that POST to the Vite server to create a new contact, let’s use client side routing instead.

取消触发传统表达事件处理,把这个url链接交给路由处理。

创建Contacts

Tutorial v6.4.2 | React Router

root.tsx

import {
  Outlet,
  Link,
  useLoaderData,
  Form,
} from "react-router-dom";
import { getContacts, createContact } from "../contacts";

export async function action() {
  await createContact();
}

/* other code */

export default function Root() {
  const { contacts } = useLoaderData();
  return (
    <>
      
    
  );
}

导出了一个事件处理函数action

main.ts

定义路由时,添加action属性

import Root, {
  loader as rootLoader,
  action as rootAction,
} from "./routes/root";

const router = createBrowserRouter([
  {
    path: "/",
    element: ,
    errorElement: ,
    loader: rootLoader,
    action: rootAction,
    children: [
      {
        path: "contacts/:contactId",
        element: ,
      },
    ],
  },
]);

效果

react router6.x官方DEMO_第6张图片

能够添加联系人了,但是联系人属性没有保存。

The createContact method just creates an empty contact with no name or data or anything. But it does still create a record, promise!

Wait a sec … How did the sidebar update? Where did we call the action? Where’s the code to refetch the data? Where are useState, onSubmit and useEffect?!

这就是“old school web”编程模式出现的地方。正如我们前面所讨论的,阻止浏览器将请求发送到服务器,而是将其发送到您的路由操作。在web语义中,POST通常意味着某些数据正在发生变化。按照约定,React Router将此作为提示,在操作完成后自动重新验证页面上的数据。这意味着所有的useLoaderData钩子都会更新,UI会自动与您的数据保持同步!很酷。

点击record显示对应信息

点击联系人record后,链接变成contacts/xxxx

看一下路由声明:

[
  {
    path: "contacts/:contactId",
    element: ,
  },
];

These params are passed to the loader with keys that match the dynamic segment. For example, our segment is named :contactId so the value will be passed as params.contactId.

These params are most often used to find a record by ID. Let’s try it out.

contact.tsx

使用params

import { Form, useLoaderData } from "react-router-dom";
import { getContact } from "../contacts";

export async function loader({ params }) {
  return getContact(params.contactId);
}

export default function Contact() {
  // const contact = {
  //   first: "Your",
  //   last: "Name",
  //   avatar: "https://placekitten.com/g/200/200",
  //   twitter: "your_handle",
  //   notes: "Some notes",
  //   favorite: true,
  // };
  const contact = useLoaderData();
  // existing code
}

效果

react router6.x官方DEMO_第7张图片

数据存储

react router6.x官方DEMO_第8张图片

localforage

编辑数据

Just like creating data, you update data withForm. Let’s make a new route at contacts/:contactId/edit. Again, we’ll start with the component and then wire it up to the route config.

新建组件edit.tsx

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

export default function EditContact() {
  const contact = useLoaderData();

  return (
    

Name