React.js 和 Spring Data REST:第 2 部分 - 超媒体
要查看此代码的更新,请访问我们的 React.js 和 Spring Data REST 教程。 |
在上一节中,您了解了如何使用 Spring Data REST 搭建一个后端工资服务来存储员工数据。它缺少一个关键功能,即使用超媒体控件和通过链接导航。相反,它硬编码了查找数据的路径。
您可以随意从该仓库中获取代码并跟着操作。本节基于上一节的应用,并增加了一些额外内容。
起初有数据…然后有了 REST
越来越多的人将任何基于 HTTP 的接口称为 REST API,这让我感到沮丧。今天的例子是 SocialSite REST API。那是 RPC。它完全是 RPC…。需要做些什么才能让 REST 架构风格明确超文本是一种约束的概念?换句话说,如果应用程序状态引擎(以及 API)不是由超文本驱动的,那么它就不能是 RESTful 的,也不能是 REST API。就这么简单。是否有什么损坏的手册需要修正?
那么,超媒体控件,即超文本,到底是什么,以及如何使用它们?要弄清楚这一点,让我们退后一步,看看 REST 的核心使命。
REST 的概念是借鉴使网络如此成功的思想并将其应用于 API。尽管网络规模庞大、性质动态,并且客户端(即浏览器)更新速度慢,但网络取得了惊人的成功。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 提供了分页支持。要使用它,只需调整仓库定义即可
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
}
您的接口现在扩展了 PagingAndSortingRepository
,它增加了设置页面大小的额外选项,并添加了用于页面跳转的导航链接。后端的其余部分是相同的(除了一些额外的预加载数据以增加趣味性)。
重启应用(./mvnw spring-boot:run
)并看看它是如何工作的。
$ curl localhost:8080/api/employees?size=2 { "_links" : { "first" : { "href" : "http://localhost:8080/api/employees?page=0&size=2" }, "self" : { "href" : "http://localhost:8080/api/employees" }, "next" : { "href" : "http://localhost:8080/api/employees?page=1&size=2" }, "last" : { "href" : "http://localhost:8080/api/employees?page=2&size=2" } }, "_embedded" : { "employees" : [ { "firstName" : "Frodo", "lastName" : "Baggins", "description" : "ring bearer", "_links" : { "self" : { "href" : "http://localhost:8080/api/employees/1" } } }, { "firstName" : "Bilbo", "lastName" : "Baggins", "description" : "burglar", "_links" : { "self" : { "href" : "http://localhost:8080/api/employees/2" } } } ] }, "page" : { "size" : 2, "totalElements" : 6, "totalPages" : 3, "number" : 0 } }
默认页面大小为 20,所以要查看实际效果,应用 ?size=2
。正如预期,只列出了两名员工。此外,还有一个 first、next 和 last 链接。还有一个 self 链接,它不包含任何上下文,包括页面参数。
如果您导航到 next 链接,您还会看到一个 prev 链接
$ curl "http://localhost:8080/api/employees?page=1&size=2" { "_links" : { "first" : { "href" : "http://localhost:8080/api/employees?page=0&size=2" }, "prev" : { "href" : "http://localhost:8080/api/employees?page=0&size=2" }, "self" : { "href" : "http://localhost:8080/api/employees" }, "next" : { "href" : "http://localhost:8080/api/employees?page=2&size=2" }, "last" : { "href" : "http://localhost:8080/api/employees?page=2&size=2" } }, ...
注意
|
在 URL 查询参数中使用“&”时,命令行会认为它是换行符。用引号将整个 URL 括起来以绕过此问题。 |
这看起来很简洁,但当您更新前端来利用这一点时,效果会更好。
按关系导航
就这样!无需再对后端进行更改即可开始使用 Spring Data REST 开箱即用的超媒体控件。您可以切换到前端工作。(这就是 Spring Data REST 的一部分美妙之处。无需繁琐的控制器更新!)
注意
|
需要指出的是,这个应用并不是“Spring Data REST 特有的”。相反,它使用了 HAL、URI Templates 以及其他标准。这就是为什么使用 rest.js 如此轻松的原因:该库自带 HAL 支持。 |
在上一节中,您硬编码了 /api/employees
的路径。相反,您应该硬编码的 ONLY 路径是根路径。
...
var root = '/api';
...
借助一个方便小巧的follow()
函数,您现在可以从根目录开始并导航到所需位置!
componentDidMount: function () {
this.loadFromServer(this.state.pageSize);
},
在上一节中,加载是直接在 componentDidMount()
内部完成的。在本节中,我们将使其能够在更新页面大小时重新加载整个员工列表。为此,我们已将相关内容移至 loadFromServer()
中。
loadFromServer: function (pageSize) {
follow(client, root, [
{rel: 'employees', params: {size: pageSize}}]
).then(employeeCollection => {
return client({
method: 'GET',
path: employeeCollection.entity._links.profile.href,
headers: {'Accept': 'application/schema+json'}
}).then(schema => {
this.schema = schema.entity;
return employeeCollection;
});
}).done(employeeCollection => {
this.setState({
employees: employeeCollection.entity._embedded.employees,
attributes: Object.keys(this.schema.properties),
pageSize: pageSize,
links: employeeCollection.entity._links});
});
},
loadFromServer
与上一节非常相似,但它使用了 follow()
- follow() 函数的第一个参数是用于进行 REST 调用的
client
对象。 - 第二个参数是起始根 URI。
- 第三个参数是要导航的关系数组。每个关系可以是字符串或对象。
关系数组可以像 ["employees"]
一样简单,这意味着在进行第一次调用时,在 _links 中查找名为 employees 的关系(或 rel)。找到其 href 并导航到它。如果数组中还有其他关系,重复此过程。
有时,仅凭 rel 本身是不够的。在这段代码片段中,它还传入了一个查询参数 ?size=<pageSize>。还有其他选项可以提供,正如您稍后将看到的那样。
获取 JSON Schema 元数据
使用基于 size 的查询导航到 employees 后,employeeCollection 触手可及。在上一节中,我们将其视为一天的工作结束,并在 <EmployeeList />
中显示了数据。今天,您将进行另一次调用,以获取在 /api/profile/employees
找到的一些 JSON Schema 元数据。
您可以自己查看数据
$ curl http://localhost:8080/api/profile/employees -H 'Accept:application/schema+json' { "title" : "Employee", "properties" : { "firstName" : { "title" : "First name", "readOnly" : false, "type" : "string" }, "lastName" : { "title" : "Last name", "readOnly" : false, "type" : "string" }, "description" : { "title" : "Description", "readOnly" : false, "type" : "string" } }, "definitions" : { }, "type" : "object", "$schema" : "https://json-schema.fullstack.org.cn/draft-04/schema#" }
注意
|
/profile/employees 的默认元数据格式是 ALPS。但在本例中,您使用了内容协商来获取 JSON Schema。 |
通过在 <App />
组件的状态中捕获此信息,您可以在稍后构建输入表单时很好地利用它。
创建新记录
有了这些元数据,您现在可以向 UI 添加一些额外的控件。创建一个新的 React 组件,<CreateDialog />
。
var CreateDialog = React.createClass({
handleSubmit: function (e) {
e.preventDefault();
var newEmployee = {};
this.props.attributes.forEach(attribute => {
newEmployee[attribute] = React.findDOMNode(this.refs[attribute]).value.trim();
});
this.props.onCreate(newEmployee);
// clear out the dialog's inputs
this.props.attributes.forEach(attribute => {
React.findDOMNode(this.refs[attribute]).value = '';
});
// Navigate away from the dialog to hide it.
window.location = "#";
},
render: function () {
var inputs = this.props.attributes.map(attribute…