GraphQL 第一章 查詢和變更
What ? When ? Where ? Why ? How ?
GraphQL 是什麼?
GraphQL 是一個用於 API 的查詢語言,是一個使用基於類型系統來執行查詢的服務端運行時。
GraphQL 包括兩部分:定義的類型和類型上的字段(Schema),每個類型上的每個字段的解析函數(Resolvers)
爲什麼要用 GraphQL ?
1. GraphQL 是強類型的 Schema 。是每個 GraphQL API 的基礎,它清晰的定義了每個 API 支持的操作,包括輸入的參數和返回的內容
2. 按需獲取。從 API 獲取想要的數據,不必依賴 REST 端口返會的固定數據結構。獲取的結果是可預測的。解決了 Overfetching 和 Underfetching
- Overfetching 意味着前端得到了實際不需要的數據,造成了性能和帶寬的浪費
- Underfetching 獲取的數據中缺少,需要通過額外的請求去獲取需要的數據。
3. GraphQL 支持快速產品開發。前端開發可以使用諸多庫(APollo、Relay 或Urql),可以用緩存、實時更新UI等功能
4. GraphQL 可以組合使用。Schema 拼接是 GraphQL 的新概念之一。可以組合和鏈接多個 GraphQL API 合併爲一個(一個 GraphQL API 可以由多個 GraphQL API 構成),避免了與多個 GraphQL 端點的通信,只需處理單個 API 端點,而協調與各種服務通信的複雜性從前端隱藏了。
5. 完善的社區資源。比如graphql-tools 等,可以在 nodejs 環境中運行,同時可以與 express 和 koa 結合使用。
什麼時候用 GraphQL ?哪裏用 GraphQL ?
用不用取決於你自己,在哪裏用也看你自己了
GraphQL API 的目的
創建API的目的是使自己的軟件具有可以被其他外部服務集成的能力。即使你的程序被單個前端程序所使用,也可以將此前端視爲外部服務,爲此,當通過API爲兩者之間提供通信時,你能夠在不同的項目中工作。
如果你在一個大型團隊中工作,可以將其拆分爲創建前端和後端團隊,從而允許他們使用相同的技術,並使他們的工作更輕鬆。
怎麼去用 GraphQL ?
GraphQL 查詢能夠遍歷相關對象及其字段,使得客戶端可以一次請求查詢大量相關數據,而不像傳統 REST 架構中那樣需要多次往返查詢。
{
hero {
name
# 查詢的備註
frinds {
name
}
}
}
# 最後的結果結構
{
data: {
hero:{
name:"xxxx",
friends:[
{name:'yyy'},
{name:'zzzz'}
]
}
}
}
在查詢的過程中,我們還會根據我們傳遞的參數來進行查詢
每一個字段和嵌套對象都能有自己的一組參數,從而使得 GraphQL 可以完美替代多次 API 獲取請求。甚至你也可以給 標量(scalar)字段傳遞參數,用於實現服務端的一次轉換,而不用每個客戶端分別轉換。
# 基本的傳參
{
hero(id: 1){
name
}
}
# 轉換傳遞過來的數據
{
hero(id: 1){
name
height(unit: FOOT) # 結果:5.2245 這樣的浮點數 FOOT 是一個枚舉類型,之後會講到
}
}
我們會遇到這樣一種情況,就是無法通過不同參數來查詢相同的字段,因此這裏我們就用到了別名(Aliases)
# 這樣的情況下我們就無法區分結果了
{
hero(id: 1) { id, name, nickname, age }
hero(id :2) { id, name, nickname, age }
}
# 因此我們這裏就可以用到別名
{
hero1: hero(id: 1) { id, name, nickname, age }
hero2: hero(id :2) { id, name, nickname, age }
}
# -----> 結果就通過別名進行區分了
{
data: {
hero1:{ name: 'xxxx', ...},
hero2:{ name: 'zzzz', ...}
}
}
我們發現,上看我們在獲取數據的時候,重複寫了 id, name, nickname, age
這些字段,這其實還好,字段不算多,如果我們定義的一個對象是很複雜的,並且字段繁多的,我們雖然通過 CV 大法可以簡單的就複製過來,但是這樣的操作很 low,因此我們可以想到一種方式,你就是把重複的字段進行一個抽離封裝。graphql 中就提供了相應的處理方式片段(Fragments)
注:片段不能引用其自身或者創建迴環,因爲會導致結果無邊界
{
hero1: hero(id: 1) { ...person }
hero2: hero(id :2) { ...person }
}
fragment person on Charactor {
id
name
nickname
age
}
# 在片段內也是可以使用變量的
fragment person on Charactor {
id
name
nickname
age
getFriend(id: $id){ # 變量馬上就要講了
name
}
}
上面我們也寫了好些個 graphql 的查詢,但是上面這些都是簡寫的形式,我們省略了操作類型以及查詢的名稱
操作類型:有三種分別是query 、 mutation 、subscription
查詢名稱:這個是自己定義的,爲了讓你的操作更加語義話,方便閱讀
之前我們在獲取某些特定數據的時候會傳遞參數,但是我們傳遞的參數是寫死的,但是實際中我們肯定會動態的傳遞某些參數,因此就需要用變量的方式來動態接受相應的數據。
修改我們上面通過 id 獲取相應 hero 的代碼
注:當我們調用該查詢的時候我們怎麼把實參與形參對應起來呢?我們需要通過
$
後面的那個名字來進行匹配,就像下面的那個我們傳遞的時候就需要 {id: 1}
# { "graphiql": true, "variables": { "id": 1 } }
query getHeroById($id: ID!){
hero(id: $id){
name
}
}
變量定義看上去像是上述查詢中的 ($id: ID!)。其工作方式跟類型語言中函數的參數定義一樣。它以列出所有變量,變量前綴必須爲 id: ID! = 1)`
變量讓我們可以避免手動傳值,但是我們有時候也需要一個方式來動態的改變我們的查詢結果,這時候就引入了一個概念 —— 指令:可以附着在字段或者片段包含的字段上,然後以任何服務端期待的方式來改變查詢的執行
graphql 核心規範包含了兩個指令: @include 和 @skip
* @include(if: Boolean) 僅在參數爲 true 時,包含此字段。
* @skip(if: Boolean) 如果參數爲 true ,跳過此字段。
query getHeroById($id: ID!){
hero(id: $id){
name
friends @include(if: $id > 2){
name
}
}
}
Graphql 也允許你自己定義指令,來完成某些操作。
上面也說到了,操作類型有三種,我們剛纔用的都是 query 的,現在我們來了解一下 mutation,我們在實際中肯定不僅僅只是查詢的操作,CRUD 這四種操作是無可避免的,我們就來講講變更數據。就如同查詢一樣,如果任何變更字段返回一個對象類型,你也能請求其嵌套字段。獲取一個對象變更後的新狀態也是十分有用的。
# 標量方式傳參
mutation createHero($name: name, $age: age, $gender: gender){
createAHero(name: $name, age: $age, $gender: gender){
id
name
age
gender
}
}
# 輸入對象類型方式傳參
mutation createHero($userInfo: userInfo){
createAHero(userInfo: $userInfo){
id
name
age
gender
}
}
# userInfo
{
name: 'zs',
age: 12,
gender: 'male'
}
查詢和變更之間名稱之外的一個重要區別是:查詢字段時,是並行執行,而變更字段時,是線性執行,一個接着一個。
這意味着如果我們一個請求中發送了兩個 createHero 變更,第一個保證在第二個之前執行,以確保我們不會出現競態。
內聯片段(inline fragment)
GraphQL schema 具備定義接口和聯合類型的能力,如果你查詢的字讀啊返回的是接口或者聯合類型,那麼你可能需要使用內聯片段來取出下層具體類型的數據
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
這個查詢中,hero 字段返回 Character 類型,取決於 episode 參數,其可能是 Human 或者 Droid 類型。在直接選擇的情況下,你只能請求 Character 上存在的字段,譬如 name。
如果要請求具體類型上的字段,你需要使用一個類型條件內聯片段。因爲第一個片段標註爲 … on Droid,primaryFunction 僅在 hero 返回的 Character 爲 Droid 類型時纔會執行。同理適用於 Human 類型的 height 字段。
具名片段也可以用於同樣的情況,因爲具名片段總是附帶了一個類型。
某些情況下你並不知道你從 GraphQL 服務獲取到的是什麼類型,所以 GraphQL 允許你在查詢的任何位置請求 __typename
注意是兩個_
,這是一個元字段,用來獲得那個位置的對象類型名稱
{
search(text: "an") {
__typename
... on Human {
name
}
... on Droid {
name
}
... on Starship {
name
}
}
}
# 獲取的結果
{
"data": {
"search": [
{
"__typename": "Human",
"name": "Han Solo"
},
{
"__typename": "Human",
"name": "Leia Organa"
},
{
"__typename": "Starship",
"name": "TIE Advanced x1"
}
]
}
}
GraphQL 服務提供了不少元字段,之後會講到