距離Facebook發佈新版Relay(Relay Modern)已經快一年時間了,但相關的中文資料與實踐案例依然不是不少。究其緣由,可能和官方文檔不夠詳細有關。本文經過對GraphQL與Relay的淺析,但願能下降其上手難度,同時也便於判斷,本身的業務是否適合使用Relay框架。
什麼?直接上代碼?猴~能夠Github克隆Relay應用模版,裏面整合了先後端和路由,基本能知足常見App的需求。html
GraphQL是一套獨立的數據查詢系統,關於它的介紹與使用,官方網站已有比較詳細的介紹,同時,如今已有中文版能夠參考。對於基本概念,建議直接閱讀官網,本文不作詳細介紹。前端
之因此名稱中包含有Graph,是由於GraphQL採用了圖結構的查詢方式。以一個例子來看:
咱們想要設計一個公司的內部人員管理系統,假設一種最簡單的場景,至少會包含部門和員工兩大信息。以圖的結構來表示他們的關係的話,可能會是這樣:git
{
員工(ID: "022") {
姓名
職位
所屬部門 {
名稱
}
同事 {
ID
姓名
職位
}
}
}
複製代碼
正常狀況下,服務端返回的結果是:github
{
員工 {
姓名: "L某",
職位: "員工",
所屬部門:{
名稱: "前端部"
}
同事:[
{
ID: "022",
姓名: "S某",
職位: "經理"
},
{
ID: "033",
姓名: "Y某",
職位: "總監"
}
]
}
}
複製代碼
能夠看到,根據查詢請求,員工的信息以Json的格式所有返回出來了。對應到圖中,實際上是提取了藍色的這部分信息: 數據庫
{
員工(ID: "022") {
姓名
職位
...
}
部門(名稱: "設計部") {
名稱
}
}
複製代碼
返回結果:redux
{
員工 {
姓名: "L某",
職位: "員工",
...
}
部門 {
名稱: "設計部"
}
}
複製代碼
在實際的應用開發中,也一般採用以上這樣的設計結構。
到此,咱們來總結一下。GraphQL經過查詢語句,將圖結構的數據,提取成了樹狀結構做爲結果返回。這裏的圖結構的數據,對應的即是GraphQL的類型系統。而如何將圖中的節點,也就是定義的各個類型相互關聯,須要經過具體的邏輯代碼來實現。官方和社區也提供了各類語言的GraphQL庫。
那麼還有個問題,若是我想修改數據該怎麼辦呢?
爲解決這個問題,GraphQL引入了Mutation的概念。咱們能夠把Mutation看做是一種特殊的查詢,你須要爲它定義名稱、參數、返回數據,並在具體的代碼邏輯中,完成它的具體數據操做。詳細能夠參考官方文檔。segmentfault
官方網站簡單介紹了GraphQL的一些優勢,不過,你可能更想知道,相比其餘的API設計模式,GraphQL有什麼優點呢?
根據我實踐下來的理解,GraphQL主要解決了面向前端的API在開發和後期維護中,常會遇到的一些矛盾點。下面,咱們以RESTful API爲參照,來具體看一下。後端
再來看上一節的例子,像獲取員工所有信息這樣的場景在應用開發中仍是比較常見的,若是換成RESTful的API,會是怎麼樣的呢?我想,通常會有兩種作法:設計模式
咱們先看第一種作法。這種方式有幾點明顯的弊端:
首先是信息的冗餘。若是我在其餘地方只須要顯示員工基礎信息,不包含具體的部門或同事信息,那就存在了數據的冗餘。
其次,從後端角度,會帶來維護上的成本。因爲前端的展現需求相對多變,極可能會形成許多再也不使用的API,而這些API又每每不敢輕易移除。又或者,在新需求中,極可能會新增與現有API重複度較高的API,形成後端業務代碼的冗餘。
再者,即便考慮經過參數的方式,能使得返回值有可選擇性,確實能夠增長必定的靈活度,但又不可避免的增長了參數的複雜性。
再看第二種作法。經過這種細粒度的模塊劃分方式,相對第一種來講,減輕了後端代碼的維護成本,但卻對前端極不友好。好比,一個列表中的某個字段,後端只提供了單獨查詢的方式,當列表數據量特別大的時候,請求數也大大增長,將直接影響前端性能與用戶體驗。此外,大量的異步請求無疑增長了前端代碼複雜度,從而提升了前端的維護成本。
而GraphQL只須要定義好類型及對應的數據處理方式,暴露給查詢根節點,前端能夠隨意按需請求,很好的解決了以上的矛盾。數組
GraphQL用來做爲BFF層(Backend For Frontend)有其先天的優點,最主要在於其面向前端的友好性設計。
除了上面提到的,在查詢請求中,請求數的減小對前端體驗上的提高以外,Mutation一樣減小了請求次數。在Restful API中,除了GET請求,其它的請求完成後,前端一般還須要再發出一個GET請求,來拉取變動後的數據。若是在返回結果中,爲前端的顯示界面而增長了一部分數據,又會破壞後端代碼的可複用性。而在GraphQL的Mutation中,返回的數據徹底能夠根據前端界面的數據需求來決定,並且只需一次請求便可。結合Relay框架,還能夠定義理想化更新來減小Loading界面出現的次數,進一步提升用戶體驗。(這一點會在下一節具體展開)
在當下流行的先後端分離開發模式下,先後端開發者的溝通成本也是影響項目進度的一大要素。一般,爲了下降溝通成本,後端開發者須要提早定義API文檔,前端會根據API文檔來MOCK數據以開發前端界面。後端開發者還須要經過各類工具,如PostMan來進行測試。在先後端完成開發後,還須要作聯調對接。
對於GraphQL來講,schema自身就是很好的文檔。同時,官方還提供了一個相似於PostMan的工具GraphiQL,能夠有助於開發中的調試。
GraphQL與Relay框架結合後,還能發揮出更大優點,好比先後端一致的類型校驗、前端緩存等,具體請看下一節內容。
Relay是一套基於GraphQL和React的框架,它將這二者結合,在原來React組件的基礎上,進一步將請求封裝進組件。
官方提供了一個TodoMVC的demo能夠參考,基本涵蓋了CRUD操做。
Relay框架提供了QueryRenderer這樣一個高階組件(HOC)來封裝React組件和GraphQL請求。這個組件接受四個Props:environment、query、variables以及render。 environment須要配置網絡請求和Store;query接受的即是GraphQL請求;variables接受GraphQL請求中須要的變量,最後render用來定義組件的渲染。
假設咱們要開發一個顯示員工基本信息的Relay組件,那麼它可能會是這樣的:
<QueryRenderer
environment={environment}
query={graphql`
query StaffQuery($id: ID!) {
員工(ID: $id) {
ID
姓名
職位
}
}
`}
variables={{ id: '011' }}
render={({error, props}) => {
if (error) {
return <div>{error.message}</div>;
} else if (props) {
return <div>工號:{props["員工"]["ID"]};姓名:{props["員工"]["姓名"]};職位:{props["員工"]["職位"]};</div>;
}
return <div>Loading...</div>;
}
}
/>
複製代碼
如今咱們已有了一個展現員工基本信息的組件,若是咱們如今要在這個組件的基礎上,進一步封裝出一個員工列表的組件,該怎麼辦呢?
參照React組件的方式,能夠建立一個新的組件,接收一個包含員工ID數組的props,在這個新的組件內部,根據ID數組Map多個員工信息的Relay組件。
這樣彷佛能夠,但問題是,若是有10個ID,那這樣一個組件也就會發出10個GraphQL請求,顯然違背了GraphQL的設計理念。
固然也能夠建立一個新的Relay組件:query中直接請求一組員工數據,渲染出列表。但這樣就失去了組件的複用性,由於很顯然,這個新組件中,顯示每條員工信息的邏輯和樣式,跟單個員工信息的組件是一致的。
這裏,Relay提供了一個Fragment的HOC組件,它接受兩個Props:component和fragmentSpec。
component接受React組件,用來處理具體的組件視圖和邏輯;fragmentSpec則是接受一段GraphQL Fragment。所謂Fragment,對應到上一節的圖中,就是節點的某一部分。好比:
fragment 員工信息 on 員工 {
ID
姓名
職位
}
複製代碼
在請求中,就能夠這樣引入Fragment:
{
員工(ID: "022") {
...員工信息
}
}
複製代碼
那麼回到Relay中,能夠這樣建立一個員工信息的Fragment組件:
createFragmentContainer(
class 員工信息 extends React.Component {
render() {
return <div>工號:{this.props.data["員工"]["ID"]};姓名:{this.props.data["員工"]["姓名"]};職位:{this.props.data["員工"]["職位"]};</div>;
}
},
graphql`
fragment 員工信息 on 員工 {
ID
姓名
職位
}
`,
)
複製代碼
有了這樣一個員工信息的Fragment組件後,咱們能夠再建立員工信息列表的組件:
<QueryRenderer
environment={environment}
query={graphql`
query StaffListQuery($ids: [ID]!) {
員工(IDs: $ids) {
...員工信息
}
}
`}
variables={{ id: ['011', '022', '033'] }}
render={({error, props}) => {
if (error) {
return <div>{error.message}</div>;
} else if (props) {
return <員工信息 data={this.props.data} />;
}
return <div>Loading...</div>;
}
}
/>
複製代碼
這樣一來,組件實際的請求仍是隻有一條,但員工信息的組件獲得了成功複用,若是在其餘組件中,須要顯示員工信息,也一樣只須要將該Fragment組件引入便可。
除了基本的Fragment Container,Relay還提供了Refetch Container和Pagination Container組件,前者在原Fragment組件的基礎上,注入了refetch方法,以便知足組件須要更新數據的場景(如:用戶主動點擊數據列表的刷新按鈕);然後者,則添加了若干分頁的操做,這裏就不具體展開了。
在QueryRenderer中配置的environment裏面,主要包含的是網絡請求和Store。這裏的Store於Redux的Store不太一致。Redux中主要用來統一管理組件的State,而Relay Store則記錄的是Record。這裏的Record,其實就是GraphQL的每一個Type,或者對應於上一節的圖中的每一個節點。
當Relay框架收到GraphQL返回的數據後,會爲每個節點數據記錄一個ID,並在Relay Store中存爲一個Record。同時,Relay也爲這些Record提供了CRUD的方法。具體能夠參考官方文檔。
爲了便於在組件中發起GraphQL Mutation操做,Relay提供了commitMutation方法。除了發起Mutation以外,利用Relay Store,能夠方便的定位頁面數據並進行更新,還可以實現理想更新,進一步提高用戶體驗。
到這裏,基本已經涵蓋了Relay的所有功能。從上文能夠看出,在GraphQL原有的優點基礎上,Relay還帶來了如下兩點優點:
除此以外,結合Flow框架的類型檢測,Relay能夠很好地根據後端提供的schema作類型校驗,避免一些潛在的Bug。
經過以上的介紹和分析,相信你對GraphQL與Relay已經有了大體的瞭解,我認爲,Relay比較適合的場景,是那種前端數據展現類別衆多,且變化較大的應用,好比社交網站。但具體是否在項目中應用,仍是須要結合需求實際來決定。