React Router 完美教程(下)

我们书接上回,继续我们的React Router 路由之路:
我们到目前为止都没有用到 state、useEffect、redux等状态管理器。但也达到了我们的设计目的。

注意,action 返回的结果 可以在组件中使用 useActionData() 来获取。就像 useLoaderData() 的使用一样。

loader 中的 URL参数

接着上回的示例文件, 新建几个无名联系人。虽然联系人信息没有变化,但我们仔细查看地址栏,会发现 ID 是会发生变化的。因为我们在createContact()中为每个联系人分配了一个ID号。

我们再次查看路由信息,

...
{path: "contacts/:contactId",element: <Contact />,
},...

在上节中我们已经详细的说过,通过 :contactId 路由变量,我们可以获取到这个符在地址中的 id 号的。我们再次对 utils.jsx 进行修改:注意里面的备注信息,很重要。

import { getContacts, createContact, getContact } from "./contacts";// 模拟网络请求,获取数据
export async function rootLoader() {const contacts = await getContacts();return { contacts };
}//创建新的联系人
export async function rootAction() {const contact = await createContact();return { contact };
}// 根据 URL 中的ID 获取对应的联系人信息。变量 params 是一个对象,包含了 URL 中的参数。
// 其中 contactId 就是路由变量中的 :contactId,即 /contacts/:contactId。同名变量。
export async function contactLoader({ params }) {const contact = await getContact(params.contactId);return { contact };
}

为了不造成理解上的概念混淆,我们把原先的action 更名为 rootAction, 新增了 contactLoader函数,你应该理解过来了,这个loader是用有 组件的路由配置中的。 然后在 routerConfig.jsx中进行修改。

import { createBrowserRouter } from "react-router-dom";import Root from "./Root";
import Error404 from "./Error404";
import Contact from "./Contact";
import {rootLoader,rootAction,contactLoader,
} from "./utils";const router = createBrowserRouter([{path: "/",element: <Root />,errorElement: <Error404 />,loader: rootLoader,action: rootAction,children: [{path: "contacts/:contactId",element: <Contact />,loader: contactLoader},]}
]);export default router;

现在组件<Contact/> 加载后就能获取到相应的 contact 信息了,我们只要在组件内用 useLoaderData() 就能获取到。对Contact.jsx进行修改:

// Contact.jsximport {Form,useLoaderData,
} from "react-router-dom";
import Favorite from "./Favorite";export default function Contact() {const { contact } = useLoaderData();// const contact = {//     first: "Your",//     last: "Name",//     avatar: "https://placekitten.com/g/200/200",//     twitter: "your_handle",//     notes: "Some notes",//     favorite: true,// };...}

将之前表态定义的联系人信息删掉,用 上面的替换。 上面我已经注释了。

更新联系人信息

就像创建数据一样,您可以使用 更新数据。创建 EditContact.jsx 文件。

// Edit.jsximport { Form, useLoaderData } from "react-router-dom";export default function EditContact() {const { contact } = useLoaderData();return (<Form method="post" id="contact-form"><p><span>Name</span><inputplaceholder="First"aria-label="First name"type="text"name="first"defaultValue={contact.first}/><inputplaceholder="Last"aria-label="Last name"type="text"name="last"defaultValue={contact.last}/></p><label><span>Twitter</span><inputtype="text"name="twitter"placeholder="@jack"defaultValue={contact.twitter}/></label><label><span>Avatar URL</span><inputplaceholder="https://reactrouter.com/_docs/tutorial/12.webp"aria-label="Avatar URL"type="text"name="avatar"defaultValue={contact.avatar}/></label><label><span>Notes</span><textareaname="notes"defaultValue={contact.notes}rows={6}/></label><p><button type="submit">Save</button><button type="button">Cancel</button></p></Form>);
}

增加路由信息,将编辑组件添加到路由中

// routerConfig.jsximport { createBrowserRouter } from "react-router-dom";import Root from "./Root";
import Error404 from "./Error404";
import Contact from "./Contact";
import EditContact from "./EditContact";
import {rootLoader,rootAction,contactLoader,
} from "./utils";const router = createBrowserRouter([{path: "/",element: <Root />,errorElement: <Error404 />,loader: rootLoader,action: rootAction,children: [{path: "contacts/:contactId",element: <Contact />,loader: contactLoader},{path: "contacts/:contactId/edit",element: <EditContact />,loader: contactLoader,},]}
]);export default router;

配置在子路由中的目的就是在 Root 中的 <Outlet/>的位置中显示的。
注意路由的配置,当我们点击 Edit 按钮后,注意查看 URL 的变化,如:http://localhost:5173/contacts/uiahrwo/edit, 一眼就能看出可以匹配 ath: "contacts/:contactId/edit"的配置信息。
现在在界面应该如下所示:
在这里插入图片描述

现在我人需要将编辑的信息反映到 action 中来更新信息。在 utils.jsx 中创建 editAction(), 并配置到路由中。文件中的参数我已经作了详细的说明。

// utils.jsximport { redirect } from "react-router-dom";
import { getContacts, createContact, getContact, updateContact } from "./contacts";...// 更新联系人信息
// request 是一个对象,包含了请求的所有信息,包括请求头、请求体等。
// params 是一个对象,包含了 URL 中的参数,即路由变量。
// 通过 request.formData() 可以获取到表单数据,返回一个 FormData 对象。
// FormData 对象是一个键值对集合,每个键对应一个值,值可以是字符串,也可以是 Blob 对象。
// 通过 Object.fromEntries() 可以将 FormData 对象转换为一个普通的对象。
// 通过 updateContact() 更新联系人信息。
// redirect() 可以重定向到指定的 URL。
export async function editAction({ request, params }) {const formData = await request.formData();const updates = Object.fromEntries(formData);await updateContact(params.contactId, updates);return redirect(`/contacts/${params.contactId}`);
}

更新路由配置信息:

// routerConfig.jsximport { createBrowserRouter } from "react-router-dom";import Root from "./Root";
import Error404 from "./Error404";
import Contact from "./Contact";
import EditContact from "./EditContact";
import {rootLoader,rootAction,contactLoader,editAction,
} from "./utils";const router = createBrowserRouter([{path: "/",element: <Root />,errorElement: <Error404 />,loader: rootLoader,action: rootAction,children: [{path: "contacts/:contactId",element: <Contact />,loader: contactLoader},{path: "contacts/:contactId/edit",element: <EditContact />,loader: contactLoader,action: editAction},]}
]);export default router;

增加活动链接状态

现在我们有一堆记录,不清楚我们在侧边栏中查看的是哪一条。我们可以使用 NavLink 来解决这个问题。
Root 中的 Link 替换为 NavLink ,如下所示:

import {Outlet,Link,useLoaderData,Form,NavLink,
} from 'react-router-dom';export default function Root() {const { contacts } = useLoaderData();return (<><div id="sidebar"><h1>React Router Contacts</h1><div><form id="search-form" role="search"><inputid="q"aria-label="Search contacts"placeholder="Search"type="search"name="q"/><divid="search-spinner"aria-hiddenhidden={true}/><divclassName="sr-only"aria-live="polite"></div></form><Form method="post"><button type="submit">New</button></Form></div><nav>{contacts.length ? (<ul>{contacts.map((contact) => (<li key={contact.id}><NavLinkto={`contacts/${contact.id}`}className={({ isActive, isPending }) =>isActive? "active": isPending? "pending": ""}>{contact.first || contact.last ? (<>{contact.first} {contact.last}</>) : (<i>No Name</i>)}{" "}{contact.favorite && <span></span>}</NavLink></li>))}</ul>) : (<p><i>No contacts</i></p>)}</nav></div><div id="detail"> <Outlet /> </div></>);
}

请注意,我们将一个函数传递给 className 。当用户位于 NavLink 中的 URL 时,isActive 将为 true 。当它即将处于活动状态(数据仍在加载)时,isPending将为 true 。这使我们能够轻松指示用户的位置,并就已点击但仍在等待数据加载的链接提供即时反馈。

有时候我们希望数据在加载的时候有个状态反馈,如下图所示:
在这里插入图片描述

路由的加载状态可以通过 useNavigation 来获取。我们再次对 Root.jsx 进修修改:

import {// 其它代码// ... ...useNavigation,
} from "react-router-dom";// ...export default function Root() {const { contacts } = useLoaderData();const navigation = useNavigation();return (<><div id="sidebar">{/* ... */}</div><divid="detail"className={navigation.state === "loading" ? "loading" : ""}><Outlet /></div></>);
}

由于我们在本地缓存了数据,所以加载速度很快,看不到这个效果,但如果是从网络上执行耗时的操作时,这种效果就很明显了。

删除联系人

现在我们再来完善我们的应用,当我们点击删除按钮后,我们要把 联系人的 ID 传递给路由,再由相应的action去完成删除工作。

查看 Contact.jsx 文件

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

仔细查看<Form>中的参数, action 提交到 destroy 路由,这个路由地址是相对地址。由于Contact 的路由是 contacts/:contactId, 所以这个destroy的路径为 contacts/:contactId/destroy,
我们先创建这个路由的action函数: deleteAction(),

// utils.jsximport { redirect } from "react-router-dom";
import {getContacts,createContact,getContact,updateContact,deleteContact
} from "./contacts";...// 删除联系人
export async function destroyAction({ params }) {await deleteContact(params.contactId);return redirect("/");
}

并将它配置到路由中

 ... import { action as destroyAction } from "./routes/destroy";const router = createBrowserRouter([{path: "/",...children: [...{path: "contacts/:contactId/destroy",action: destroyAction,},],},
]);...

由于我们没有在子路由中配置 errorElement ,这是因为我们在destroyAction中直接重定向到 " / ", 所以这个element就没有创建的必要了。事实上也的确没有这个需求

 // routerConfig.jsx[...{path: "contacts/:contactId/destroy",action: destroyAction,errorElement: <div>Oops! There was an error.</div>,},
];

还有一点非常重要,Form 的 提交方法为 post 时才能激活路由中的action操作。

索引路由

每当我们加载这个App时,你会发现右侧是一个空页面。就像下面这样:
在这里插入图片描述

当一条路由有子路由时,而当前页面又处在父路由层级时,<Outlet>没有任何子路由与之匹配,所以就没有可渲染的界面。这个时候可以将索引路由视为填充该空间的默认子路由。
创建 Index 组件如下所示

// Index.jsxexport default function Index() {return (<p id="zero-state">This is a demo for React Router.<br />Check out{" "}<a href="https://reactrouter.com">the docs at reactrouter.com</a>.</p>);
}

配置这个Index到路由

// routerConfig.jsx...import Index from "./Index";...const router = createBrowserRouter([{path: "/",element: <Root />,errorElement: <Error404 />,loader: rootLoader,action: rootAction,children: [{ index: true, element: <Index /> },...]}
]);export default router;

这个时候我们进入App重新渲染后界面如下:
在这里插入图片描述

完美。
现在还有最后一个功能没有完成,就是搜索功能,我们希望根据输入的关键词来搜索出联系人。

URL的搜索参数与Get提交

传统的html表单中,如果没有指定提交方法的话默认为get方式提交到服务器,如我们Root.jsx中搜索框部分,这自然不是我们想要的结果,我们只是想把输入参数反应到地址栏中而不影响浏览器的变化,正好,Form 可以做到。我们把Root中的 html 元素 form 改成 React Router 中的 Form组件就好了,就像下面这样:

// Root.jsx
...
<Form id="search-form" role="search"><inputid="q"aria-label="Search contacts"placeholder="Search"type="search"name="q"/><divid="search-spinner"aria-hiddenhidden={true}/><divclassName="sr-only"aria-live="polite"></div>
</Form>
...

现在你的搜索栏中输入些内容回车后,你会发现浏览器的地址栏信息也会发会变化,但浏览器并没有网络请求操作。这正是我们目的,
现在我们修改 rootLoader()函数, 以获取get参数,并根据这个参数做出筛选联系人的反应:

// utils.jsx...// 模拟网络请求,获取数据
export async function rootLoader({request}) {const url = new URL(request.url);const q = url.searchParams.get("q");const contacts = await getContacts(q);return { contacts };
}
...

现在就很美了。
在这里插入图片描述

再次强调:因为这是一个 get 的,而不是 postReact Router 路由器不调用 action 。提交 GET 表格与单击链接相同:只有URL更改。这就是为什么我们添加的过滤代码在loader中,而不是在 action 中。

这也意味着这是一个普通的页面导航。您可以单击“后退”按钮以返回到自己的位置。

最后

关于路由的主要应用技术我已经讲完了,根据这些应用方法,配置 React的状态管理,会使我们的应用更加灵活,设计更加方便。只要多练习,多思考,就一定能开发出非常完美的产品。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/444095.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

天软因子数据库新成员——天软指数基金因子库

新闻资讯 News information UPDATE 天软指数基金因子库 天软始终致力于构建完善而丰富的因子库服务体系&#xff0c;陆续推出了股票因子、基金因子、多因子系列等众多因子数据及评价数据。 2024年天软将继续推进各品种因子的深入研究&#xff0c;开年之际天软推出指数基金因子库…

基于EdgeWorkers的边缘应用如何进行单元测试?

随着各行各业数字化转型的持续深入&#xff0c;越来越多企业开始选择将一些应用程序放在距离最终用户更近的边缘位置来运行&#xff0c;借此降低延迟&#xff0c;提高应用程序响应速度&#xff0c;打造更出色的用户体验。 相比传统集中部署和运行的方式&#xff0c;这种边缘应…

769933-15-5,Biotin aniline,可以合成多种有机化合物和聚合物

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;769933-15-5&#xff0c;Biotin aniline&#xff0c;生物素苯胺&#xff0c;生物素-苯胺 一、基本信息 产品简介&#xff1a;Biotin Aniline&#xff0c;一种具有重要生物学功能的化合物&#xff0c;不仅参与了维生…

Fiddler修改https请求与响应 bug修复变灰了选不了等 Fiddle对夜神模拟器抓包设置

不要修改别人的东西&#xff0c;不要修改别人的东西&#xff0c;不要修改别人的东西 只用于自己的网站&#xff0c;自己安全调试。 fiddler修改https请求 1、打到要改的请求 2、替换请求内容 3、开启捕获。操作产生请求。 4、fiddler里查看请求或响应数据 &#xff0c;确认成…

python给word插入脚注

1.需求 最近因为工作需要&#xff0c;需要给大量文本的脚注插入内容&#xff0c;我就写了个小程序。 2.实现 下面程序是我已经给所有脚注插入了两次文本“幸福”&#xff0c;给脚注2到4再插入文本“幸福” from win32com import clientdef add_text_to_specific_footnotes(…

服务器系统安装时无法进入引导界面处理【笔记】

服务器系统安装时无法进入安装界面处理&#xff1b; 服务器系统安装时无法进入安装界面处理&#xff1b; 1、按“e”键进入编辑模式。 2、在出现的编辑项里&#xff0c;找到quiet aplash后空格加入nomodeset&#xff1b; 3、然后按CTRLX或F10引导系统&#xff0c;启动之后…

地毯填补问题

地毯填补问题 题目描述 相传在一个古老的阿拉伯国家里&#xff0c;有一座宫殿。宫殿里有个四四方方的格子迷宫&#xff0c;国王选择驸马的方法非常特殊&#xff0c;也非常简单&#xff1a;公主就站在其中一个方格子上&#xff0c;只要谁能用地毯将除公主站立的地方外的所有地…

结构体的学习

结构体与共用体&#xff0c;枚举 1.数据类型复习&#xff1a; 2结构体. eg&#xff1b;统计全校同学信息 需要记录的点--- 姓名&#xff0c;班级&#xff0c;性别&#xff0c;成绩&#xff0c;年龄 统计名字&#xff1a;char s[ ] [ 100 ] { "Tmo" } …

41、WEB攻防——通用漏洞XMLXXE无回显DTD实体伪协议代码审计

文章目录 XXE原理&探针&利用XXE读取文件XXE带外测试XXE实体引用XXE挖掘XXE修复 参考资料&#xff1a;CTF XXE XXE原理&探针&利用 XXE用到的重点知识是XML&#xff0c;XML被设计为传输和存储数据&#xff0c;XML文档结构包括XML声明、DTD文档类型定义&#xf…

【MySQL】学习并使用聚合函数和DQL进行分组查询

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-t8K8tl6eNwqdFmcD {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

网络层 IP协议(1)

前置知识 主机:配有IP地址,但是不进行路由控制的设备 路由器:既配置了IP地址,又能进行路由控制的设备 节点:主机和路由器的总称 IP协议主要完成的任务就是 地址管理和路由选择 地址管理:使用一套地址体系,将网络设备的地址描述出来 路由选择:一个数据报如何从源地址到目的地址 …

开发数据产品+AI产品通关上岸课程

该课程全面解析数据产品和人工智能产品的开发与设计。学员将学习产品规划、数据分析以及AI技术应用&#xff0c;通过案例实践掌握产品开发流程&#xff0c;致力于帮助他们成功进入数据和人工智能产品领域。 课程大小&#xff1a;9.8G 课程下载&#xff1a;https://download.cs…