世界看點:React.js 和 Spring Data REST(二)

2022-12-29 14:23:50 來源:51CTO博客

第 2 部分 - 超媒體控件

在上一節,您了解了如何使用 Spring Data REST 創建后端工資單服務來存儲員工數據。它缺乏的一個關鍵功能是使用超媒體控件和鏈接導航。相反,它對路徑進行硬編碼以查找數據。

隨意獲取代碼從此存儲庫并繼續操作。本節基于上一節的應用程序,并添加了額外的內容。


(資料圖片僅供參考)

一開始,有數據...然后是休息

我對將任何基于 HTTP 的接口稱為 REST API 的人數感到沮喪。今天的例子是SocialSite REST API。那就是RPC。它尖叫著RPC。需要做些什么來使 REST 架構風格明確超文本是一種約束的概念?換句話說,如果應用程序狀態引擎(以及 API)不是由超文本驅動的,那么它就不能是 RESTful 的,也不能是 REST API。時期。是否有一些損壞的手冊需要修復?

- 羅伊·T·菲爾丁?https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

那么,究竟什么是超媒體控件(即超文本)以及如何使用它們?為了找到答案,我們退后一步,看看REST的核心使命。

REST的概念是借用使網絡如此成功的想法,并將其應用于API。盡管網絡規模龐大,動態性質和客戶端(即瀏覽器)的更新速度低,但網絡取得了驚人的成功。Roy Fielding試圖利用它的一些限制和功能,看看這是否能提供類似的API生產和消費擴張。

其中一個約束是限制動詞的數量。對于 REST,主要的是 GET、POST、PUT、DELETE 和 PATCH。還有其他的,但我們不會在這里討論它們。

GET:在不更改系統的情況下獲取資源的狀態POST:創建一個新資源而不說明位置PUT:替換現有資源,覆蓋已有的任何其他資源(如果有的話)刪除:刪除現有資源PATCH:更改現有資源(部分更改而不是創建新資源)

這些是具有眾所周知規范的標準化 HTTP 動詞。通過拾取和使用已經創造的HTTP操作,我們不需要發明一種新的語言并教育行業。

REST 的另一個約束是使用媒體類型來定義數據的格式。與其每個人都寫自己的方言來交換信息,不如開發一些媒體類型。最受歡迎的接受之一是 HAL,媒體類型 .它是Spring Data REST的默認媒體類型。一個關鍵值是 REST 沒有集中的單一媒體類型。相反,人們可以開發媒體類型并插入并試用它們。隨著不同的需求出現,行業可以靈活地移動。??application/hal+json??

REST 的一個關鍵功能是包含指向相關資源的鏈接。例如,如果您正在查看訂單,則 RESTful API 將包含指向相關客戶的鏈接、指向項目目錄的鏈接,以及指向下訂單的商店的鏈接。在本節中,您將介紹分頁,并了解如何使用導航分頁鏈接。

從后端啟用分頁

若要開始使用前端超媒體控件,需要打開一些額外的控件。Spring Data REST提供分頁支持。要使用它,請按如下方式調整存儲庫定義:

例 16。src/main/java/com/greglturnquist/payroll/EmployeeRepository.java

public interface EmployeeRepository extends PagingAndSortingRepository {}

您的界面現在擴展了 ,它添加了額外的選項來設置頁面大小,并添加了導航鏈接以從一個頁面跳到另一個頁面。后端的其余部分是相同的(除了一些??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特有的”。相反,它使用哈爾,URI 模板等標準。這就是為什么使用 rest.js 輕而易舉的原因:該庫帶有 HAL 支持。

在上一節中,您將路徑硬編碼為 。相反,您應該硬編碼的唯一路徑是根,如下所示??/api/employees??

...var root = "/api";...

用一個方便的小follow()功能,您現在可以從根目錄開始并導航到所需的位置,如下所示:

componentDidMount() {  this.loadFromServer(this.state.pageSize);}

在上一節中,加載是直接在 內部完成的。在本節中,我們可以在頁面大小更新時重新加載整個員工列表。為此,我們將內容移至 ,如下所示:??componentDidMount()????loadFromServer()??

loadFromServer(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()??

函數的第一個參數是用于進行 REST 調用的對象。??follow()????client??第二個參數是要從中開始的根 URI。第三個參數是要導航的關系數組。每個都可以是字符串或對象。

關系數組可以像 一樣簡單,這意味著在進行第一次調用時,查找名為 的關系(或)。找到它并導航到它。如果數組中存在其他關系,請重復該過程。??["employees"]????_links????rel????employees????href??

有時,僅靠 a 是不夠的。在此代碼片段中,它還插入了 的查詢參數。您還可以提供其他選項,稍后您將看到。??rel?????size=??

抓取 JSON 架構元數據

使用基于大小的查詢導航到 后,可以使用。在上一節中,我們在 中顯示了該數據。在本節中,您將執行另一個調用以獲取一些??employees????employeeCollection???????JSON 架構元數據在 中找到。??/api/profile/employees/??

您可以通過運行以下命令(與其輸出一起顯示)自行查看數據:??curl??

$ 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.org/draft-04/schema#"}

元數據的默認形式為??/profile/employees??阿爾卑斯山.但是,在這種情況下,您將使用內容協商來獲取 JSON 架構。

通過在“”組件的狀態中捕獲此信息,您可以稍后在構建輸入表單時充分利用它。

創建新記錄

有了這些元數據,現在可以向 UI 添加一些額外的控件。你可以從創建一個新的 React 組件開始,如下所示:????

class CreateDialog extends React.Component {  constructor(props) {    super(props);    this.handleSubmit = this.handleSubmit.bind(this);  }  handleSubmit(e) {    e.preventDefault();    const newEmployee = {};    this.props.attributes.forEach(attribute => {      newEmployee[attribute] = ReactDOM.findDOMNode(this.refs[attribute]).value.trim();    });    this.props.onCreate(newEmployee);    // clear out the dialog"s inputs    this.props.attributes.forEach(attribute => {      ReactDOM.findDOMNode(this.refs[attribute]).value = "";    });    // Navigate away from the dialog to hide it.    window.location = "#";  }  render() {    const inputs = this.props.attributes.map(attribute =>      

); return (
Create
X

Create new employee

{inputs}
) }}

這個新組件同時具有函數和預期函數。??handleSubmit()????render()??

我們以相反的順序深入研究這些函數,首先看函數。??render()??

渲染

代碼映射在屬性中找到的 JSON 架構數據,并將其轉換為元素數組。??attributes????

??

??key??再次被 React 需要來區分多個子節點。這是一個簡單的基于文本的輸入字段。??placeholder??讓我們向用戶顯示字段是哪個。您可能習慣于擁有屬性,但這不是必需的。使用 React,是抓取特定 DOM 節點的機制(你很快就會看到)。??name????ref??

這表示組件的動態性質,由從服務器加載數據驅動。

此組件的頂層內部是一個錨標記和另一個 .錨標記是用于打開對話框的按鈕。嵌套的是隱藏對話框本身。在此示例中,您使用的是純 HTML5 和 CSS3。完全沒有JavaScript!您可以??

????
????
??請參閱CSS代碼用于顯示和隱藏對話框。我們不會在這里深入探討。

嵌套在窗體中的窗體中注入了輸入字段的動態列表,然后是“創建”按鈕。該按鈕具有事件處理程序。這是注冊事件處理程序的 React 方式。??

????notallow={this.handleSubmit}??

React 不會在每個 DOM 元素上創建事件處理程序。相反,它有一個性能更高,更復雜溶液。您不需要管理該基礎結構,而是可以專注于編寫功能代碼。

處理用戶輸入

該函數首先阻止事件在層次結構中進一步冒泡。然后,它使用相同的 JSON 架構屬性屬性來查找每個 ,方法是使用 。??handleSubmit()????????React.findDOMNode(this.refs[attribute])??

??this.refs???是一種按名稱獲取特定 React 組件的方法。請注意,您只獲得虛擬 DOM 組件。要獲取實際的 DOM 元素,您需要使用 .??React.findDOMNode()??

在遍歷每個輸入并構建對象后,我們為新員工記錄調用回調。這個函數在里面,并作為另一個屬性提供給這個 React 組件。看看這個頂級函數是如何運作的:??newEmployee????onCreate()????App.onCreate??

onCreate(newEmployee) {  follow(client, root, ["employees"]).then(employeeCollection => {    return client({      method: "POST",      path: employeeCollection.entity._links.self.href,      entity: newEmployee,      headers: {"Content-Type": "application/json"}    })  }).then(response => {    return follow(client, root, [      {rel: "employees", params: {"size": this.state.pageSize}}]);  }).done(response => {    if (typeof response.entity._links.last !== "undefined") {      this.onNavigate(response.entity._links.last.href);    } else {      this.onNavigate(response.entity._links.self.href);    }  });}

再一次,我們使用該函數導航到執行 POST 操作的資源。在這種情況下,不需要應用任何參數,因此基于字符串的實例數組很好。在此情況下,將返回調用。這允許下一個子句處理 .??follow()????employees????rel????POST????then()????POST??

新記錄通常會添加到數據集的末尾。由于您正在查看某個頁面,因此期望新員工記錄不在當前頁面上是合乎邏輯的。要處理此問題,您需要獲取應用了相同頁面大小的新一批數據。該承諾在里面的最后一個條款中返回。??done()??

由于用戶可能希望查看新創建的員工,因此您可以使用超媒體控件并導航到該條目。??last??

首次使用基于承諾的 API?承諾是一種啟動異步操作,然后注冊函數以在任務完成時響應的方法。承諾被設計為鏈接在一起以避免“回調地獄”。請看以程:

when.promise(async_func_call())  .then(function(results) {    /* process the outcome of async_func_call */  })  .then(function(more_results) {    /* process the previous then() return value */  })  .done(function(yet_more) {    /* process the previous then() and wrap things up */  });

有關更多詳細信息,請查看這個關于承諾的教程.

關于 promise 要記住的秘密是,函數需要返回一些東西,無論是值還是另一個 promise。 函數不返回任何內容,并且您不會在 1 之后鏈接任何內容。如果你還沒有注意到,(這是 from rest.js 的一個實例)和函數返回 promise。??then()????done()????client????rest????follow??

分頁數據

您已經在后端設置了分頁,并且在創建新員工時已經開始利用它。

在上一節,您使用頁面控件跳轉到頁面。將其動態應用于 UI 并讓用戶根據需要導航非常方便。根據可用的導航鏈接動態調整控件會很棒。??last??

首先,讓我們看看您使用的函數:??onNavigate()??

onNavigate(navUri) {  client({method: "GET", path: navUri}).done(employeeCollection => {    this.setState({      employees: employeeCollection.entity._embedded.employees,      attributes: this.state.attributes,      pageSize: this.state.pageSize,      links: employeeCollection.entity._links    });  });}

這是在頂部的 .同樣,這是為了允許在頂部組件中管理 UI 的狀態。在傳遞給 React 組件后,以下處理程序被編碼以處理某些按鈕的單擊:??App.onNavigate????onNavigate()??????

handleNavFirst(e){  e.preventDefault();  this.props.onNavigate(this.props.links.first.href);}handleNavPrev(e) {  e.preventDefault();  this.props.onNavigate(this.props.links.prev.href);}handleNavNext(e) {  e.preventDefault();  this.props.onNavigate(this.props.links.next.href);}handleNavLast(e) {  e.preventDefault();  this.props.onNavigate(this.props.links.last.href);}

這些函數中的每一個都截獲默認事件并阻止它冒泡。然后,它使用正確的超媒體鏈接調用該函數。??onNavigate()??

現在,您可以根據以下超媒體鏈接中顯示的鏈接有條件地顯示控件:??EmployeeList.render??

render() {  const employees = this.props.employees.map(employee =>      );  const navLinks = [];  if ("first" in this.props.links) {    navLinks.push();  }  if ("prev" in this.props.links) {    navLinks.push();  }  if ("next" in this.props.links) {    navLinks.push();  }  if ("last" in this.props.links) {    navLinks.push();  }  return (    
{employees}
First Name Last Name Description
{navLinks}
)}

與上一節一樣,它仍會轉換為組件數組。然后它構建一個數組作為 HTML 按鈕數組。??this.props.employees????????navLinks??

因為 React 是基于 XML 的,所以你不能把元素放進去。必須改用編碼版本。?????

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

刪除現有記錄

刪除條目要容易得多。您需要做的就是獲取其基于 HAL 的記錄并應用于其鏈接:??DELETE????self??

class Employee extends React.Component {  constructor(props) {    super(props);    this.handleDelete = this.handleDelete.bind(this);  }  handleDelete() {    this.props.onDelete(this.props.employee);  }  render() {    return (              {this.props.employee.firstName}        {this.props.employee.lastName}        {this.props.employee.description}                                    )  }}

此更新版本的 Employee 組件在行末尾顯示一個額外的條目(刪除按鈕)。它被注冊為在單擊時調用。然后,該函數可以調用傳遞的回調,同時提供上下文重要的記錄。??this.handleDelete????handleDelete()????this.props.employee??

這再次表明,在一個地方管理頂部組件中的狀態是最容易的。情況可能并非總是如此。但是,通常,在一個位置管理狀態可以更輕松地保持簡單。通過使用特定于組件的詳細信息 () 調用回調,非常容易編排組件之間的行為。??this.props.onDelete(this.props.employee)??

通過將函數追溯到頂部,您可以看到它是如何工作的:??onDelete()????App.onDelete??

onDelete(employee) {  client({method: "DELETE", path: employee._links.self.href}).done(response => {    this.loadFromServer(this.state.pageSize);  });}

使用基于頁面的 UI 刪除記錄后要應用的行為有點棘手。在這種情況下,它會從服務器重新加載整個數據,應用相同的頁面大小。然后它顯示第一頁。

如果要刪除最后一頁上的最后一條記錄,它將跳轉到第一頁。

調整頁面大小

了解超媒體如何真正發光的一種方法是更新頁面大小。Spring Data REST 根據頁面大小流暢地更新導航鏈接。

在 的頂部有一個 HTML 元素: 。??ElementList.render??????

??ref="pageSize"??使使用 輕松獲取該元素。this.refs.pageSize??defaultValue??使用狀態的 .pageSize?onInput??注冊處理程序,如下所示:
handleInput(e) {  e.preventDefault();  const pageSize = ReactDOM.findDOMNode(this.refs.pageSize).value;  if (/^[0-9]+$/.test(pageSize)) {    this.props.updatePageSize(pageSize);  } else {    ReactDOM.findDOMNode(this.refs.pageSize).value =      pageSize.substring(0, pageSize.length - 1);  }}

它可以阻止事件冒泡。然后它使用 的屬性來查找 DOM 節點并提取其值,所有這些都是通過 React 的輔助函數完成的。它通過檢查輸入是否是一串數字來測試輸入是否真的是一個數字。如果是這樣,它會調用回調,將新的頁面大小發送到 React 組件。否則,剛輸入的字符將從輸入中刪除。??ref????????findDOMNode()????App??

當它得到一個 ?看看吧:??App????updatePageSize()??

updatePageSize(pageSize) {  if (pageSize !== this.state.pageSize) {    this.loadFromServer(pageSize);  }}

由于新的頁面大小值會導致所有導航鏈接發生更改,因此最好重新獲取數據并從頭開始。

將一切整合在一起

有了所有這些不錯的補充,你現在有一個真正被吸干的 UI,如下圖所示:

您可以在頂部看到頁面大小設置,每行上的刪除按鈕以及底部的導航按鈕。導航按鈕演示了超媒體控件的強大功能。

在下圖中,您可以看到將元數據插入到 HTML 輸入占位符中:??CreateDialog??

這確實顯示了使用超媒體與域驅動的元數據(JSON 模式)相結合的強大功能。網頁不必知道哪個字段是哪個字段。相反,用戶可以看到它并知道如何使用它。如果將其他字段添加到域對象,此彈出窗口將自動顯示該字段。??Employee??

回顧

在本節中:

您打開了 Spring Data REST 的分頁功能。你拋棄了硬編碼的 URI 路徑,并開始使用根 URI 和關系名稱或“rels”。您更新了 UI 以動態使用基于頁面的超媒體控件。你添加了創建和刪除員工以及根據需要更新 UI 的功能。您可以更改頁面大小并讓 UI 靈活響應。

問題?

您使網頁動態化。但是打開另一個瀏覽器選項卡并將其指向同一應用程序。一個選項卡中的更改不會更新另一個選項卡中的任何內容。

我們將在下一節中討論該問題。

第 3 部分 - 條件操作

在上一節,您了解了如何打開 Spring Data REST 的超媒體控件,如何讓 UI 通過分頁進行導航,以及如何根據更改頁面大小動態調整大小。您添加了創建和刪除員工以及調整頁面的功能。但是,如果不考慮其他用戶對您當前正在編輯的相同數據所做的更新,任何解決方案都是不完整的。

隨意獲取代碼從此存儲庫并繼續操作。本節基于上一節,但添加了額外的功能。

放還是不放?這就是問題所在。

當您獲取資源時,風險是如果其他人更新它,它可能會過時。為了解決這個問題,Spring Data REST集成了兩種技術:資源版本控制和ETag。

通過在后端對資源進行版本控制并在前端使用 ETag,可以有條件地進行更改。換句話說,您可以檢測資源是否已更改,并防止 (或 ) 踩踏其他人的更新。??PUT????PUT????PATCH??

對 REST 資源進行版本控制

若要支持資源的版本控制,請為需要此類型保護的域對象定義版本屬性。下面的清單顯示了如何對對象執行此操作:??Employee??

例 17.src/main/java/com/greglturnquist/payroll/Employee.java

@Entitypublic class Employee {  private @Id @GeneratedValue Long id;  private String firstName;  private String lastName;  private String description;  private @Version @JsonIgnore Long version;  private Employee() {}  public Employee(String firstName, String lastName, String description) {    this.firstName = firstName;    this.lastName = lastName;    this.description = description;  }  @Override  public boolean equals(Object o) {    if (this == o) return true;    if (o == null || getClass() != o.getClass()) return false;    Employee employee = (Employee) o;    return Objects.equals(id, employee.id) &&      Objects.equals(firstName, employee.firstName) &&      Objects.equals(lastName, employee.lastName) &&      Objects.equals(description, employee.description) &&      Objects.equals(version, employee.version);  }  @Override  public int hashCode() {    return Objects.hash(id, firstName, lastName, description, version);  }  public Long getId() {    return id;  }  public void setId(Long id) {    this.id = id;  }  public String getFirstName() {    return firstName;  }  public void setFirstName(String firstName) {    this.firstName = firstName;  }  public String getLastName() {    return lastName;  }  public void setLastName(String lastName) {    this.lastName = lastName;  }  public String getDescription() {    return description;  }  public void setDescription(String description) {    this.description = description;  }  public Long getVersion() {    return version;  }  public void setVersion(Long version) {    this.version = version;  }  @Override  public String toString() {    return "Employee{" +      "id=" + id +      ", firstName="" + firstName + "\"" +      ", lastName="" + lastName + "\"" +      ", description="" + description + "\"" +      ", version=" + version +      "}";  }}
該字段用 注釋。它會導致每次插入和更新行時自動存儲和更新值。versionjavax.persistence.Version

當獲取單個資源(不是集合資源)時,Spring Data REST 會自動添加一個ETag 響應標頭與此字段的值。

獲取單個資源及其標頭

在上一節,您使用集合資源來收集數據并填充 UI 的 HTML 表。使用Spring Data REST,數據集被視為數據的預覽。雖然對于瀏覽數據很有用,但要獲取像 ETag 這樣的標頭,您需要單獨獲取每個資源。??_embedded??

在此版本中,更新為提取集合。然后,可以使用 URI 檢索每個單獨的資源:??loadFromServer??

例 18。src/main/js/app.js - 獲取每個資源

loadFromServer(pageSize) {  follow(client, root, [ (1)    {rel: "employees", params: {size: pageSize}}]  ).then(employeeCollection => { (2)    return client({      method: "GET",      path: employeeCollection.entity._links.profile.href,      headers: {"Accept": "application/schema+json"}    }).then(schema => {      this.schema = schema.entity;      this.links = employeeCollection.entity._links;      return employeeCollection;    });  }).then(employeeCollection => { (3)    return employeeCollection.entity._embedded.employees.map(employee =>        client({          method: "GET",          path: employee._links.self.href        })    );  }).then(employeePromises => { (4)    return when.all(employeePromises);  }).done(employees => { (5)    this.setState({      employees: employees,      attributes: Object.keys(this.schema.properties),      pageSize: pageSize,      links: this.links    });  });}

?1

該函數轉到集合資源。??follow()????employees??

?2

第一個子句創建一個調用來提取 JSON 架構數據。它有一個內部 then 子句,用于在組件中存儲元數據和導航鏈接。??then(employeeCollection ? …)??????

請注意,此嵌入式承諾返回 .這樣,集合可以傳遞到下一個調用,讓您在此過程中獲取元數據。??employeeCollection??

?3

第二個子句將員工的集合轉換為一系列承諾來獲取每個單獨的資源。這是為每個員工獲取 ETag 標頭所需的內容。??then(employeeCollection ? …)????GET??

?4

該子句采用 promise 數組,并將它們合并為一個 promise 中,當解析所有 GET 承諾時,將解析該承諾。??then(employeePromises ? …)????GET????when.all()??

?5

??loadFromServer???總結使用此數據合并更新 UI 狀態的位置。??done(employees ? …)??

該鏈也在其他地方實現。例如,(用于跳轉到不同頁面)已更新為獲取單個資源。由于它與此處顯示的內容基本相同,因此已將其排除在本節之外。??onNavigate()??

更新現有資源

在本節中,您將添加一個 React 組件來編輯現有的員工記錄:??UpdateDialog??

例 19。src/main/js/app.js - UpdateDialog 組件

class UpdateDialog extends React.Component {  constructor(props) {    super(props);    this.handleSubmit = this.handleSubmit.bind(this);  }  handleSubmit(e) {    e.preventDefault();    const updatedEmployee = {};    this.props.attributes.forEach(attribute => {      updatedEmployee[attribute] = ReactDOM.findDOMNode(this.refs[attribute]).value.trim();    });    this.props.onUpdate(this.props.employee, updatedEmployee);    window.location = "#";  }  render() {    const inputs = this.props.attributes.map(attribute =>      

); const dialogId = "updateEmployee-" + this.props.employee.entity._links.self.href; return (
Update
X

Update an employee

{inputs}
) }};

這個新組件同時具有函數和預期函數,類似于組件。??handleSubmit()????render()??????

我們以相反的順序深入研究這些函數,首先看一下函數。??render()??

渲染

此組件使用與上一節相同的 CSS/HTML 策略來顯示和隱藏對話框。????

它將 JSON 架構屬性數組轉換為 HTML 輸入數組,這些輸入包含在段落元素中以進行樣式設置。這也與 相同,但有一個區別:字段加載了 .在組件中,字段為空。??????this.props.employee????CreateDialog??

該字段的構建方式不同。整個 UI 上只有一個鏈接,但顯示的每一行都有一個單獨的鏈接。因此,該字段基于鏈接的 URI。這在元素的 React 、HTML 錨標記和隱藏彈出窗口中使用。??id????CreateDialog????UpdateDialog????id????self????

????key??

處理用戶輸入

提交按鈕鏈接到組件的功能。這很容易用于通過使用提取彈出窗口的詳細信息??handleSubmit()????React.findDOMNode()??反應引用.

提取輸入值并將其加載到對象中后,將調用頂級方法。這延續了 React 的單向綁定風格,其中要調用的函數從上層組件推送到下層組件。這樣,狀態仍然在頂部進行管理。??updatedEmployee????onUpdate()??

有條件的看跌期權

因此,您已經竭盡全力在數據模型中嵌入版本控制。Spring Data REST已將該值作為ETag響應標頭提供。這是您可以充分利用它的地方:

例 20。src/main/js/app.js - onUpdate function

onUpdate(employee, updatedEmployee) {  client({    method: "PUT",    path: employee.entity._links.self.href,    entity: updatedEmployee,    headers: {      "Content-Type": "application/json",      "If-Match": employee.headers.Etag    }  }).done(response => {    this.loadFromServer(this.state.pageSize);  }, response => {    if (response.status.code === 412) {      alert("DENIED: Unable to update " +        employee.entity._links.self.href + ". Your copy is stale.");    }  });}

A 與??PUT??If-Match請求標頭導致 Spring Data REST 根據當前版本檢查該值。如果傳入的值與數據存儲的版本值不匹配,Spring Data REST 將失敗,并顯示 .??If-Match????HTTP 412 Precondition Failed??

規格為承諾/A+?實際上將其 API 定義為 .到目前為止,您已經看到它僅與成功函數一起使用。在前面的代碼片段中,有兩個函數。成功函數調用 ,而錯誤函數顯示有關過時數據的瀏覽器警報。??then(successFunction, errorFunction)????loadFromServer??

將一切整合在一起

定義 React 組件并將其很好地鏈接到頂級函數后,最后一步是將其連接到組件的現有布局中。??UpdateDialog????onUpdate??

在上一節中創建的被放在頂部,因為只有一個實例。但是,直接與特定員工相關聯。所以你可以在下面的 React 組件中看到它插入:??CreateDialog????EmployeeList????UpdateDialog????Employee??

例 21。src/main/js/app.js - Employee with UpdateDialog

class Employee extends React.Component {  constructor(props) {    super(props);    this.handleDelete = this.handleDelete.bind(this);  }  handleDelete() {    this.props.onDelete(this.props.employee);  }  render() {    return (              {this.props.employee.entity.firstName}        {this.props.employee.entity.lastName}        {this.props.employee.entity.description}                                                              )  }}

在本節中,將從使用集合資源切換到使用單個資源。員工記錄的字段現在位于 。它使我們能夠訪問 ,在那里我們可以找到 ETag。??this.props.employee.entity????this.props.employee.headers??

Spring Data REST還支持其他標頭(例如上次修改時間),不屬于本系列。因此,以這種方式構建數據很方便。

和 的結構僅在使用時相關??.entity????.headers??休息.js作為首選的 REST 庫。如果您使用其他庫,則必須根據需要進行調整。

看到事物的實際效果

要查看修改后的應用程序工作,請執行以下操作:

通過運行 啟動應用程序。./mvnw spring-boot:run打開瀏覽器選項卡并導航到http://localhost:8080.您應該會看到類似于下圖的頁面:拉出佛羅多的編輯對話框。在瀏覽器中打開另一個選項卡并調出相同的記錄。在第一個選項卡中更改記錄。嘗試在第二個選項卡中進行更改。您應該看到瀏覽器選項卡發生了變化,如下圖所示

通過這些修改,您可以通過避免沖突來提高數據完整性。

回顧

在本節中:

您使用基于 JPA 的樂觀鎖定字段配置了域模型。@Version您調整了前端以提取單個資源。您將單個資源的 ETag 標頭插入到請求標頭中,以使 PAT 具有條件。If-Match您為列表中顯示的每個員工編寫了新代碼。UpdateDialog

插入此插件后,很容易避免與其他用戶發生沖突或覆蓋他們的編輯。

問題?

知道您何時編輯不良記錄當然很高興。但是最好等到您單擊“提交”后再找出答案嗎?

獲取資源的邏輯在 和 中非常相似。您是否看到避免重復代碼的方法???loadFromServer????onNavigate??

您可以充分利用 JSON 架構元數據來構建 和 輸入。您是否看到其他地方可以使用元數據來使事情更加通用?假設您想向 添加另外五個字段。更新 UI 需要什么???CreateDialog????UpdateDialog????Employee.java??

標簽: 應用程序 版本控制 充分利用

上一篇:React.js 和 Spring Data REST(三)
下一篇:環球通訊!明晰C語言中的真/假和0/1