GraphQL & Relay 初探

距離Facebook發佈新版Relay(Relay Modern)已經快一年時間了,但相關的中文資料與實踐案例依然不是不少。究其緣由,可能和官方文檔不夠詳細有關。本文經過對GraphQL與Relay的淺析,但願能下降其上手難度,同時也便於判斷,本身的業務是否適合使用Relay框架。
什麼?直接上代碼?猴~能夠Github克隆Relay應用模版,裏面整合了先後端和路由,基本能知足常見App的需求。html

GraphQL

GraphQL是一套獨立的數據查詢系統,關於它的介紹與使用,官方網站已有比較詳細的介紹,同時,如今已有中文版能夠參考。對於基本概念,建議直接閱讀官網,本文不作詳細介紹。前端

設計思想

之因此名稱中包含有Graph,是由於GraphQL採用了圖結構的查詢方式。以一個例子來看:
咱們想要設計一個公司的內部人員管理系統,假設一種最簡單的場景,至少會包含部門和員工兩大信息。以圖的結構來表示他們的關係的話,可能會是這樣:git

回顧經常使用的系統會發現,基本均可以經過圖來描述角色關係。這裏,咱們能夠類比 圖數據庫的概念。因爲圖的結構更接近於天然世界,相比關係型數據庫,在設計圖數據庫時,會省去一個圖結構向關係型結構的轉化工做。關於圖數據庫的更多介紹,能夠參考 neo4j的介紹
回到上圖,假設咱們如今要查詢員工L某的詳細信息,用GraphQL,能夠這樣來請求(爲更加直觀,這裏以中文來表示):

{
  員工(ID: "022") {
    姓名
    職位
    所屬部門 {
      名稱
    }
    同事 {
      ID
      姓名
      職位
    }
  }
}
複製代碼

正常狀況下,服務端返回的結果是:github

{
  員工 {
    姓名: "L某",
    職位: "員工",
    所屬部門:{
      名稱: "前端部"
    }
    同事:[
      {
        ID: "022",
        姓名: "S某",
        職位: "經理"
      },
      {
        ID: "033",
        姓名: "Y某",
        職位: "總監"
      }
    ]
  }
}
複製代碼

能夠看到,根據查詢請求,員工的信息以Json的格式所有返回出來了。對應到圖中,實際上是提取了藍色的這部分信息: 數據庫

查詢以員工(L某)爲起點,沿着邊,將所需的關聯數據提取出來,造成了最終的返回的結果。由此能夠看出,GraphQL所作的,實際上是將 圖結構的數據提取出成爲一個樹狀結構。爲了更清晰的體現這一點,咱們將藍色部分單獨取出來,並稍稍換一下節點的位置:
這裏,可能會有個疑問:這樣查詢出的樹狀結構,必然要求涉及的節點之間有邊。 若是我想要同時查詢兩個節點的信息,但它們間沒有邊,那該怎麼辦呢?
仍以以前的例子來看,若是我想查詢員工L某和設計部的信息,但它們之間沒有邊。這種時候,能夠構建一個虛擬的節點,並以它爲起點,鏈接起其餘須要查詢的節點。大概會是這樣的一種結構:
查詢結構:

{
  員工(ID: "022") {
    姓名
    職位
    ...
  }
  部門(名稱: "設計部") {
    名稱
  }
}
複製代碼

返回結果:redux

{
  員工 {
    姓名: "L某",
    職位: "員工",
    ...
  }
  部門 {
    名稱: "設計部"
  }
}
複製代碼

在實際的應用開發中,也一般採用以上這樣的設計結構。
到此,咱們來總結一下。GraphQL經過查詢語句,將圖結構的數據,提取成了樹狀結構做爲結果返回。這裏的圖結構的數據,對應的即是GraphQL的類型系統。而如何將圖中的節點,也就是定義的各個類型相互關聯,須要經過具體的邏輯代碼來實現。官方和社區也提供了各類語言的GraphQL庫
那麼還有個問題,若是我想修改數據該怎麼辦呢?
爲解決這個問題,GraphQL引入了Mutation的概念。咱們能夠把Mutation看做是一種特殊的查詢,你須要爲它定義名稱、參數、返回數據,並在具體的代碼邏輯中,完成它的具體數據操做。詳細能夠參考官方文檔segmentfault

優點

官方網站簡單介紹了GraphQL的一些優勢,不過,你可能更想知道,相比其餘的API設計模式,GraphQL有什麼優點呢?
根據我實踐下來的理解,GraphQL主要解決了面向前端的API在開發和後期維護中,常會遇到的一些矛盾點。下面,咱們以RESTful API爲參照,來具體看一下。後端

靈活性

再來看上一節的例子,像獲取員工所有信息這樣的場景在應用開發中仍是比較常見的,若是換成RESTful的API,會是怎麼樣的呢?我想,通常會有兩種作法:設計模式

  • 一、單獨的API來拉取所有信息;
  • 二、將「同事」、「部門」這些信息做爲獨立的API,在前端經過多條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

Relay是一套基於GraphQL和React的框架,它將這二者結合,在原來React組件的基礎上,進一步將請求封裝進組件。
官方提供了一個TodoMVC的demo能夠參考,基本涵蓋了CRUD操做。

QueryRenderer

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>;
    }
  }
/>
複製代碼

Fragment

如今咱們已有了一個展現員工基本信息的組件,若是咱們如今要在這個組件的基礎上,進一步封裝出一個員工列表的組件,該怎麼辦呢?
參照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 ContainerPagination Container組件,前者在原Fragment組件的基礎上,注入了refetch方法,以便知足組件須要更新數據的場景(如:用戶主動點擊數據列表的刷新按鈕);然後者,則添加了若干分頁的操做,這裏就不具體展開了。

Relay Store

在QueryRenderer中配置的environment裏面,主要包含的是網絡請求和Store。這裏的Store於Redux的Store不太一致。Redux中主要用來統一管理組件的State,而Relay Store則記錄的是Record。這裏的Record,其實就是GraphQL的每一個Type,或者對應於上一節的圖中的每一個節點。
當Relay框架收到GraphQL返回的數據後,會爲每個節點數據記錄一個ID,並在Relay Store中存爲一個Record。同時,Relay也爲這些Record提供了CRUD的方法。具體能夠參考官方文檔

Mutations

爲了便於在組件中發起GraphQL Mutation操做,Relay提供了commitMutation方法。除了發起Mutation以外,利用Relay Store,能夠方便的定位頁面數據並進行更新,還可以實現理想更新,進一步提高用戶體驗。

優點

到這裏,基本已經涵蓋了Relay的所有功能。從上文能夠看出,在GraphQL原有的優點基礎上,Relay還帶來了如下兩點優點:

  • 一、實現了數據查詢與組件的結合,進一步提升了前端模塊化程度,提升組件複用性。
  • 二、優秀的客戶端緩存,提高用戶體驗。

除此以外,結合Flow框架的類型檢測,Relay能夠很好地根據後端提供的schema作類型校驗,避免一些潛在的Bug。

總結

經過以上的介紹和分析,相信你對GraphQL與Relay已經有了大體的瞭解,我認爲,Relay比較適合的場景,是那種前端數據展現類別衆多,且變化較大的應用,好比社交網站。但具體是否在項目中應用,仍是須要結合需求實際來決定。

參考資料

GraphQL Concepts Visualized
GraphQL and Relay 淺析

相關文章
相關標籤/搜索