React.js 和 Spring Data REST:第 2 部分

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

上一节 中,您了解了如何使用 Spring Data REST 建立后端工资单服务来存储员工数据。它缺少的一个关键功能是使用超媒体控件和链接导航。相反,它硬编码了查找数据的路径。

随意从这个存储库中 获取代码 并继续操作。此会话基于上一个会话的应用程序,添加了额外的东西。

一开始是数据……然后是 REST。


我对将任何基于 HTTP 的接口称为 REST API 的人数感到沮丧。今天的示例是 SocialSite REST API。那就是RPC。它尖叫着 RPC……需要做些什么来使 REST 架构风格在超文本是一种约束的概念上变得清晰?换句话说,如果应用程序状态引擎(以及 API)不是由超文本驱动的,那么它就不能是 RESTful,也不能是 REST API。时期。是否有一些损坏的手册需要修复?

罗伊·T·菲尔丁

那么,究竟什么是超媒体控件,即超文本,您如何使用它们?为了找出答案,让我们退后一步,看看 REST 的核心使命。

REST 的概念是借用使网络如此成功的想法并将它们应用于 API。尽管 web 规模巨大、动态特性和客户端(即浏览器)更新率低,但 web 取得了惊人的成功。 Roy Fielding 试图使用它的一些约束和特性,看看是否可以提供类似的 API 生产和消费扩展。

约束之一是限制动词的数量。对于 REST,主要的是 GET、POST、PUT、DELETE 和 PATCH。还有其他的,但我们不会在这里讨论它们。

  • GET - 在不改变系统的情况下获取资源的状态
  • POST - 创建一个新资源而不说在哪里
  • PUT - 替换现有资源,覆盖已经存在的任何其他资源(如果有的话)
  • DELETE - 删除现有资源
  • PATCH - 部分改变现有资源

这些是具有良好编写规范的标准化 HTTP 动词。通过挑选和使用已经创造的 HTTP 操作,我们不必发明一种新语言和教育行业。

REST 的另一个约束是使用媒体类型来定义数据格式。与其每个人都写自己的方言来交换信息,不如开发一些媒体类型是明智的。最流行的一种被接受的是HAL,媒体类型application/hal+json。它是 Spring Data REST 的默认媒体类型。一个敏锐的价值是 REST 没有集中的、单一的媒体类型。相反,人们可以开发媒体类型并将其插入。尝试一下。随着不同需求的出现,行业可以灵活移动。

REST 的一个关键特性是包含指向相关资源的链接。例如,如果您正在查看订单,RESTful API 将包含指向相关客户的链接、指向商品目录的链接,可能还包括指向下订单的商店的链接。在本课程中,您将介绍分页,并了解如何使用导航分页链接。

从后台开启分页


要开始使用前端超媒体控件,您需要打开一些额外的控件。 Spring Data REST 提供分页支持。要使用它,只需调整存储库定义:

src/main/java/com/greglturnquist/payroll/EmployeeRepository.java


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

您的界面现在扩展了 PagingAndSortingRepository ,它添加了额外的选项来设置页面大小,还添加了导航链接以从一个页面跳到另一个页面。后端的其余部分是相同的(除了一些 额外的预加载数据 以使事情变得有趣)。

重新启动应用程序 ( ./mvnw spring-boot:run ) 并查看它是如何工作的。


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

默认页面大小为 20,因此要查看实际效果,应用 ?size=2 。正如预期的那样,只列出了两名员工。此外,还有一个 first next last 链接。还有一个 自我 链接,没有上下文, 包括页面参数

如果导航到 下一个 链接,您还会看到 一个链接:


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

笔记 在 URL 查询参数中使用“&”时,命令行认为这是一个换行符。用引号将整个 URL 包起来以绕过它。

这看起来很整洁,但是当你更新前端以利用它时它会更好。

按关系导航

就是这样!无需对后端进行更多更改即可开始使用 Spring Data REST 提供的开箱即用的超媒体控件。您可以切换到在前端工作。 (这是 Spring Data REST 的优点之一。没有混乱的控制器更新!)

笔记 需要指出的是,这个应用程序不是“特定于 Spring Data REST 的”。相反,它使用 HAL URI Templates 和其他标准。这就是使用 rest.js 如此简单的原因:该库带有 HAL 支持。

在上一个会话中,您将路径硬编码到 /api/employees 。相反,您应该硬编码的唯一路径是根目录。


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

使用一个方便的小 follow() 函数 ,您现在可以从根开始并导航到您需要的地方!


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

在上一个会话中,加载是直接在 componentDidMount() 内部完成的。在此会话中,我们可以在更新页面大小时重新加载整个员工列表。为此,我们已将内容移至 loadFromServer() 中。


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

loadFromServer 与之前的会话非常相似,但 if 使用的 follow()

  • follow() 函数的第一个参数是用于进行 REST 调用的 client 对象。
  • 第二个参数是开始的根 URI。
  • 第三个参数是要导航的关系数组。每个都可以是字符串或对象。

关系数组可以像 ["employees"] 一样简单,这意味着在进行第一次调用时,在 _links 中查找名为 employees 的关系(或 rel )。找到它的 href 并导航到它。如果数组中还有其他关系,则冲洗并重复。

有时,一个 rel 本身是不够的。在这段代码中,它还插入了一个查询参数 ?size=<pageSize> 。还可以提供其他选项,您将在后面看到。

抓取 JSON 模式元数据

使用基于大小的查询导航到 员工 后, employeeCollection 就在您的指尖。在上一个会话中,我们将其称为 day 并在 <EmployeeList /> 中显示该数据。今天,您正在执行另一个调用以获取一些 JSON 模式元数据 ,发现 /api/profile/employees

可以自己看数据:


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

笔记 /profile/employees 中元数据的默认形式是 ALPS。不过,在本例中,您使用内容协商来获取 JSON 模式。

通过在 `<App />` 组件的状态中捕获此信息,您可以在以后构建输入表单时充分利用它。

创造新记录

配备此元数据后,您现在可以向 UI 添加一些额外的控件。创建一个新的 React 组件 <CreateDialog />


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

这个新组件既有 handleSubmit() 函数,也有预期的 render() 函数。

让我们以相反的顺序深入研究这些函数,首先看一下 render() 函数。

渲染

您的代码映射在 attributes 属性中找到的 JSON 架构数据,并将其转换为 <p><input></p> 元素的数组。

  • React 再次需要 key 来区分多个子节点。
  • 这是一个简单的基于文本的输入字段。
  • 占位符 是我们可以向用户显示字段的地方。
  • 您可能习惯于使用 名称 属性,但这不是必需的。对于 React, ref 是获取特定 DOM 节点的机制(您很快就会看到)。

这表示组件的动态特性,由从服务器加载数据驱动。

在该组件的顶级 <div> 中是一个锚标记和另一个 <div> 。锚标记是打开对话框的按钮。嵌套的 <div> 是隐藏的对话框本身。在此示例中,您使用的是纯 HTML5 和 CSS3。根本没有 JavaScript!您可以 看到用于显示/隐藏对话框的 CSS 代码 。我们不会在这里深入探讨。

位于 <div id="createEmployee"> 内的是一个表单,您的动态输入字段列表被注入其中,然后是 创建 按钮。该按钮有一个 onClick={this.handleSubmit} 事件处理程序。这是注册事件处理程序的 React 方式。

笔记 React 不会在每个 DOM 元素上创建一大堆事件处理程序。相反,它有一个 性能更高、更复杂的 解决方案。关键是您不必管理该基础架构,而是可以专注于编写功能代码。

处理用户输入

handleSubmit() 函数首先阻止事件向上冒泡。然后它使用相同的 JSON Schema attribute 特性来查找每个 <input> 使用 React.findDOMNode(this.refs[attribute])

this.refs 是一种通过名称获取特定 React 组件的方法。从这个意义上说,您只是获得了虚拟 DOM 组件。要获取实际的 DOM 元素,您需要使用 React.findDOMNode()

在遍历每个输入并构建 newEmployee 对象后,我们调用回调到新员工的 onCreate() 。这个函数位于 App.onCreate 的顶部,并作为另一个属性提供给这个 React 组件。查看顶级函数的运行方式:


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

再次使用 follow() 函数导航到执行 POST 操作的 员工 资源。在这种情况下,不需要应用任何参数,因此基于字符串的 rels 数组就可以了。在这种情况下,将返回 POST 调用。这允许下一个 then() 子句处理 POST 的结果。

新记录通常添加到数据集的末尾。由于您正在查看某个页面,因此期望新员工记录不在当前页面上是合乎逻辑的。要处理此问题,您需要获取一批应用了相同页面大小的新数据。 done() 中的最后一个子句返回该承诺。

由于用户可能希望看到新创建的员工,因此您可以使用超媒体控件并导航到 最后一个 条目。

这在我们的 UI 中引入了分页的概念。让我们接下来解决这个问题!

第一次使用基于承诺的 API? Promises 是一种启动异步操作的方式,然后注册一个函数以在任务完成时做出响应。承诺被设计成链接在一起以避免“回调地狱”。看下面的流程:


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

有关更多详细信息,请查看 有关 promises 的教程

promises 要记住的秘密是 then() 函数 需要 返回一些东西,无论是一个值还是另一个 promise。 done() 函数不会返回任何东西,你也不会在它后面链接任何东西。如果您还没有注意到, client (它是 rest.js 中 rest 的一个实例)以及 follow 函数返回承诺。

通过数据分页

您在后端设置了分页,并且在创建新员工时已经开始利用它。

上一节 中,您使用页面控件跳转到 最后 一页。将其动态应用到 UI 并让用户根据需要进行导航会非常方便。根据可用的导航链接动态调整控件会很棒。

首先,让我们检查一下您使用的 onNavigate() 函数。


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

这是在 App.onNavigate 内部的顶部定义的。同样,这是为了允许在顶级组件中管理 UI 的状态。将 onNavigate() 向下传递给 <EmployeeList /> React 组件后,将编写以下处理程序来处理某些按钮的点击:


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

这些函数中的每一个都会拦截默认事件并阻止它冒泡。然后它使用适当的超媒体链接调用 onNavigate() 函数。

现在根据 EmployeeList.render 的超媒体链接中出现的链接有条件地显示控件:


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

与上一个会话一样,它仍然将 this.props.employees 转换为 <Element /> 组件的数组。然后它构建了一个 navLinks 数组,一个 HTML 按钮数组。

笔记 因为 React 基于 XML,所以不能将“<”放在 <button> 元素中。您必须改用编码版本。

然后您可以看到 {navLinks} 插入到返回的 HTML 的底部。

删除现有记录

删除条目要容易得多。获取其基于 HAL 的记录并将 DELETE 应用于其 自身 链接。


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

这个更新版本的 Employee 组件在行的末尾显示了一个额外的条目,一个删除按钮。它注册为在单击时调用 this.handleDelete 。然后 handleDelete() 函数可以调用向下传递的回调,同时提供上下文重要的 this.props.employee 记录。

重要的 这再次表明在一个地方管理顶级组件中的状态是最容易的。情况可能并非 总是 如此,但通常情况下,在一个地方管理状态可以使保持正直和简单变得更容易。通过使用特定于组件的详细信息 ( this.props.onDelete(this.props.employee) ) 调用回调,可以很容易地协调组件之间的行为。

onDelete() 函数追溯到 App.onDelete 的顶部,您可以看到它是如何运行的:


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

使用基于页面的 UI 删除记录后应用的行为有点棘手。在这种情况下,它会从服务器重新加载全部数据,并应用相同的页面大小。然后它显示第一页。

如果你是删除最后一页的最后一条记录,它会跳转到第一页。

调整页面大小

了解超媒体真正闪耀的一种方法是更新页面大小。 Spring Data REST 根据页面大小流畅地更新导航链接。

ElementList.render 的顶部有一个 HTML 元素: <input ref="pageSize" defaultValue={this.props.pageSize} onInput={this.handleInput}/>

  • ref="pageSize" 可以很容易地通过 this.refs.pageSize 获取该元素。
  • defaultValue 使用状态的 pageSize 初始化它。
  • onInput 注册一个处理程序,如下所示。


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

它阻止事件冒泡。然后它使用 <input> ref 属性找到 DOM 节点并提取它的值,所有这些都是通过 React 的 findDOMNode() 辅助函数完成的。它通过检查输入是否是一串数字来测试输入是否真的是一个数字。如果是这样,它会调用回调,将新页面大小发送到 App React 组件。如果不是,则刚刚输入的字符会从输入中剥离。

App 在获取 updatePageSize() 时会做什么?一探究竟:


 public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

因为新的页面大小会导致所有导航链接发生变化,所以最好重新获取数据并从头开始。

把它们放在一起

有了所有这些不错的添加,您现在拥有了一个真正焕然一新的 UI。

您可以在顶部看到页面大小设置,在每行上看到删除按钮,在底部看到导航按钮。导航按钮说明了超媒体控件的强大功能。

在下方,您可以看到 CreateDialog 以及插入 HTML 输入占位符的元数据。

这确实展示了使用超媒体与域驱动元数据(JSON 模式)相结合的力量。网页不必知道哪个字段是哪个。相反,用户可以 看到 它并知道如何使用它。如果您将另一个字段添加到 Employee 域对象,此弹出窗口将自动显示它。

审查

在本届会议中:

  • 您打开了 Spring Data REST 的分页功能。
  • 您抛弃了硬编码的 URI 路径并开始使用根 URI 结合关系名称或“rels”。
  • 您更新了 UI 以动态使用基于页面的超媒体控件。
  • 您添加了创建和删除员工以及根据需要更新 UI 的功能。
  • 您可以更改页面大小并让 UI 灵活响应。

问题?

你使网页动态。但是打开另一个浏览器选项卡并将其指向同一个应用程序。一个选项卡中的更改不会更新另一个选项卡中的任何内容。

这是我们可以在下届会议上解决的问题。在那之前,祝你编码愉快!


Greg Turnquist 的教程。