GraphQL 第一章 查詢和變更

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!)。其工作方式跟類型語言中函數的參數定義一樣。它以列出所有變量,變量前綴必須爲 IDgraphqlID!!ES6(,後跟其類型,本例中爲 ID(這個類型是 graphql 中內置的一個標量類型,之後會降到類型) 所有聲明的變量都必須是標量、枚舉型或者輸入對象類型。 變量定義可以是可選的或者必要的。上面 `ID` 後面跟的 `!` 表示是必要的,如果沒有 `!` 表示是可選的。 我們也可以給這個參數一個默認值,給默認值的形式就和 ES6 中的方式是一樣的 `(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 服務提供了不少元字段,之後會講到

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章