【React Router】初识路由(中)

server/2024/10/18 13:52:07/

加载数据

这一节主要强调 URL、布局和数据 的解耦。

在根模块文件中创建并导出一个加载器函数,并配置到路由。

getContacts() 是我们自己封装的数据请求 API,新增的数据暂时存储到 localforage。

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

路由配置 loader

// 配置 loader
loader: rootLoader,

数据渲染,使用 内置的 useLoaderData() 方法

const { contacts } = useLoaderData();

数据写入

提交 HTML 表单时,它会在浏览器中引起导航事件,就像我们点击某个链接一样。而链接和提交表单的唯一的区别在于请求:链接只能更改 URL,而表单还可以更改请求方式(GET 与 POST)和请求体(POST 表单数据)。

如果没有客户端路由(也就是react-router这类的client-side routing),浏览器会自动序列化表单数据,并以不同的方式发送给服务器。对于POST请求,数据会作为请求体(request body)发送给服务器;而对于GET请求,数据会作为URLSearchParams(URL查询参数)的形式附加在URL上发送给服务器。

React Router的行为与此类似,但它不会将请求发送到服务器,而是使用客户端路由并将请求发送到路由操作action进行处理。

我们使用 React Router 封装的 <Form>,创建用户。

<Form method="post"><button type="submit">New</button>
</Form>
export async function action() {const contact = await createContact();return redirect(`/contacts/${contact.id}/edit`);
}

像 loader 一样,action 创建后也需要挂载在路由上。action: rootAction

可以简单的理解为 loader 是需要获取数据时触发的,action 则是修改数据时触发的。

创建用户信息,使用 Form submit之后,我们发现Reat Router 的<Form> 自动阻止了浏览器发送请求到服务器,而是将其发送到我们指定的路由操作action中 。然后自动触发数据重新验证的过程,也就意味着所有使用 useLoaderData 钩子的组件都会自动更新!并且UI会自动与数据保持同步!避免我们使用 useState 、 onSubmit 和 useEffect 这些操作,极大地简化了我们的操作。

URL 参数

path: 'contacts/:contactId',

:contactId URL 段。冒号 ( : ) 为“动态段”。动态段将匹配 URL 该位置上的动态(变化)值,如联系人 ID。我们将 URL 中的这些值称为“URL 参数”,简称 “params”。这些params将作为键值对传递给加载器loader,值将作为 params.contactId 传递(可以在 loader上使用)。

当然,每个组件中的 loader, action 等都需要在路由中进行配置,而且路由通常都有自己的 laoder 等,只不过他们有时可能会相同。

export async function loader({ params }) {const contact = await getContact(params.contactId);return { contact };
}export async function Contact() {const { contact } = useLoaderData();// ...
}

更新数据

<inputplaceholder="First"aria-label="First name"type="text"name="first"defaultValue={contact.first}
/>

如果没有 JavaScript,当提交表单时,浏览器会创建FormData对象,并将其设置为请求的主体(body),然后将请求发送到服务器。

如前所述,React Router 阻止了这种默认行为,而是将请求发送到我们自己想要的操作,也就是自己在组件中定义的 action中,同时包括FormData。表单中的每个字段都可以通过 formData.get(name) 访问。

这里还使用Object.fromEntries将数据全部收集到一个对象中,这正是后面自定义的 updateContact 函数想要的。

export async function action({ request, params }) {const formData = await request.formData();const updates = Object.fromEntries(formData);await updateContact(params.contactId, updates);return redirect(`/contacts/${params.contactId}`);
}

loaderaction都可以返回Response(因为它们都收到了Request!)。

redirect辅助函数只是为了更方便地返回response,告诉应用程序更改位置。

活动链接样式

<NavLinkto={`contacts/${contact.id}`}className={({ isActive, isPending }) =>isActive? "active": isPending? "pending": ""}
>

<NavLink> 是一种特殊的 <Link> ,它知道自己是否处于 “激活”、"待定 "或 "过渡 "状态。当用户访处于NavLink指定的URL时,isActive将为true。当链接即将被激活时(数据仍在加载中), isPending 将为 true。

loading 界面

当用户浏览应用时,React Router 会在为下一页加载数据时保留旧页面。也就是说这种数据模型具有客户端缓存,因此第二次导航到之前已经访问过的路由时速度会很快。

useNavigation返回当前导航状态:可以是"idle" | "submitting" | "loading"

const navigation = useNavigation();
// ...
navigation.state === "loading" ? "loading" : ""

删除数据

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

action 指向 "destroy" 。与 <Link to> 一样, <Form action> 也可以接收一个相对值。由于表单是在 contact/:contactId 中呈现的,因此点击 destroy 的相对操作将把表单提交到 contact/:contactId/destroy ,所以我们只需要在 contact/:contactId/destroy 路由下指定删除操作即可。比如:

export async function action({ params }) {await deleteContact(params.contactId);return redirect("/");
}
{path: 'contacts/:contactId/destroy',action: destroyAction,
},

详细说明,当用户点击提交按钮时:

  1. <Form> 会阻止浏览器向服务器发送新 POST 请求的默认行为,而是通过客户端路由创建一个 POST 请求来模拟浏览器的行为
  2. <Form action="destroy"> 匹配道路新路由 "contacts/:contactId/destroy" ,并向其发送请求
  3. 在操作重定向后,React Router 会调用页面上所有数据的loader,以获取最新值(这就是 “重新验证”)。 useLoaderData 返回新值,并导致组件更新!

捕获错误

export async function action({ params }) {await deleteContact(params.contactId);throw new Error("oh dang!");return redirect("/");
}

虽然我这个操作中 return redirect("/"); 根本就不会执行,但这只是为了说明如何捕获错误😁。

这样操作完成之后,发现直接如今了错误页,必须刷新才会重新加载恢复正常显示。

因此需要在路由中进行捕获:

{path: 'contacts/:contactId/destroy',action: destroyAction,errorElement: <div>Oops! There was an error.</div>,
},

因为销毁路由有自己的errorElement,并且是根路由的子路由,因此错误会在此处路由而不是根路由上呈现。这些错误会以最近的 errorElement 冒泡。只要在根路径上有一个,添加多少都行。

索引路由

如果在父路由的路径上, <Outlet> 由于没有子路由匹配,所以没有任何内容可以呈现。可以把索引路由看作是填补这一空白的默认子路由。

定义一个索引路由的页面模块 index。然后在子路由上添加:

{ index: true, element: <Index /> },

这将告诉路由,当用户位于父路由的确切路径时,路由器将匹配并呈现此路由,以保证在 <Outlet> 中没有其他子路由需要呈现时页面不为空。

导航

用一个取消功能来说明。

const navigate = useNavigate();
<buttontype="button"onClick={() => {navigate(-1);}}
>Cancel</button>

这里的 <button type="button"> 虽然看似多余,却是防止按钮提交表单的 默认 HTML 行为。所以按钮上没有event.preventDefault


以上说明中路由:

// 创建路由
const router = createBrowserRouter([{path: '/',// 将<Root>设置为根路由elementelement: <Root/>,// 将<ErrorPage>设置为根路由上的errorElementerrorElement: <ErrorPage/>,// 配置 loaderloader: rootLoader,// 配置 actionaction: rootAction,// 子路由children: [{ index: true, element: <Index /> },{path: 'contacts/:contactId',element: <Contact/>,loader: contactLoader},{path: "contacts/:contactId/edit",element: <EditContact/>,loader: contactLoader,action: editAction},{path: 'contacts/:contactId/destroy',action: destroyAction,errorElement: <div>Oops! There was an error.</div>,},]},
])

http://www.ppmy.cn/server/2903.html

相关文章

【2024官方文档版】React快速入门

系列文章目录 一、快速入门【基础】 文章目录 系列文章目录前言一、快速入门【基础】1. 创建一个组件2. 组件嵌入3.添加样式4.显示数据5.条件渲染6.渲染列表7.响应事件8.更新页面9.使用Hook10.组件间共享数据 小结&#xff1a; 前言 偶然翻开React的官方文档。觉得蛮有意思&a…

RestTemplate—微服务远程调用—案例解析

简介&#xff1a;总结来说&#xff0c;微服务之间的调用方式有多种&#xff0c;选择哪种方式取决于具体的业务需求、技术栈和架构设计。RESTful API和HTTP客户端是常见的选择&#xff0c;而Feign和Ribbon等辅助库可以简化调用过程。RPC和消息队列适用于特定的场景&#xff0c;如…

【C++】:C++关键字,命名空间,输入输出,缺省参数

目录 一&#xff0c;C关键字(C98)二&#xff0c;命名空间2.1 命名冲突2.2 关键字namespace2.2.1 命名空间中可以定义变量/函数/类型2.2.2 命名空间可以嵌套2.2.3 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。 2.3 命名空间的使用2.3.1 指定…

Vue接收接口返回的mp3格式数据并支持在页面播放音频

一、背景简介 在实际工作中需要开发一个转音频工具&#xff0c;并且能够在平台页面点击播放按钮播放音频 二、相关知识介绍 2.1 JS内置对象Blob Blob对象通常用于处理大量的二进制数据&#xff0c;可以读取/写入/操作文件、音视频等二进制数据流。Blob表示了一段不可变的二…

Vue 指令

Vue根据不同的指令&#xff0c;针对标签实现不同的功能 指令&#xff1a;带有v-前缀的特殊的标签属性 <!-- Vue指令--> <div v-html"str"></div><!-- 普通标签属性 --> <div class"box"></div> 目录 v-html v-sho…

Springboot框架——6.整合tk-mybatis

1.引入依赖&#xff1a; <dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>2.0.2</version> </dependency> 2.重写实体类&#xff1a; package com.lxj.domain;imp…

零基础如何学习linux知识

零基础学习Linux需要一个结构化和逐步深入的学习计划。以下是一个学习方向和框架仅供参考&#xff1a; 一&#xff0c; 学习方向 1. **了解Linux基础**&#xff1a; - 理解开源概念。 - 了解Linux的历史和它的重要性。 - 认识不同的Linux发行版。 2. **命令行操作*…

synchronized锁升级原理

锁升级过程 jdk1.6之后的优化 synchronized锁有四种状态&#xff0c;无锁&#xff0c;偏向锁&#xff0c;轻量级锁&#xff0c;重量级锁&#xff0c;这几个状态会随着竞争状态逐渐升级&#xff0c;锁可以升级但不能降级&#xff0c;但是偏向锁状态可以被重置为无锁状态。 1、偏…