跳至主內容

Tec。士多開發日記系列:第三篇:實作Tec記士多Frontend(GraphQL Client入門)- Part 1

Tec。士多開發日記系列:第三篇:實作Tec記士多Frontend(GraphQL Client入門)- Part 1
Andrew Shek
2022-02-21

Tec。士多開發日記系列:第三篇:實作Tec記士多Frontend(GraphQL Client入門)- Part 1

上一編Blog已經簡單講解如何實作GraphQL Server。今編Blog會深入講解Frontend如何同GraphQL Server進行溝通。Frontend最常做兩類GraphQL Query Statement,查詢(Query)及 變更(Mutation)。還有其他類型,不過坊間仍未普遍使用,所以在此不作介紹。

Query 查詢


Query Statement用途是查詢資訊,所以Query Statement是__「習慣」#__上是唔會更新數據,即是可以視為資訊是唯讀 (Read-Only)。當然可以是Query Statement上更新資訊,因為GraphQL無機制阻止的。

啟動Tec。士多 GraphQL Server

Git Clone Tec。士多 GraphQL Server Demo落自己部電腦再啟動Server。詳細方法請看Tec‧士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 2

是瀏覽器打開GraphQL Playground試下是Client Side做Query。

http://localhost:4000/graphql

GraphQL Playground

GraphQL Playground是提供一個圖形使用者介面(Graphical User Interface,簡稱GUI)做Query測試。類似於Postman同Insomnia,可以讓使用者是無Frontend的情況下對Server進利測試。

1.png

左邊Panel是編寫Query,右邊Panel是顯示查詢結果。

2.png

想知道呢台GraphQL Server提供資訊(API)俾你,可以打開最右邊"DOCS" Tab。

基本格式

是左邊Panel編寫Query。所有Query是要用"query"Tag開始。

query{
  
}

下一步,因為要諗下今次查詢的目的,所以打開"DOCS" Tab找下有無相關資料可以提供。例如,想知Tec。士多完整售賣貨品清單。

query{
  Commodities{
    
  }
}

最後,是諗一諗使用者介面(User Interface)要顯示的資料。例如,想俾使用者選擇貨品,所以今次需要貨品名稱,單位及現貨存量。

query{
  Commodities{
    name
    unit
    stockQuantity
  }
}

查詢結果是一個JSON Object。識做呢,Developers?!

{
  "data": {
    "Commodities": [
      {
        "name": "240ml經典橙汁",
        "unit": "CAN",
        "stockQuantity": 0
      },
      {
        "name": "朱古力夾心餅",
        "unit": "PACK",
        "stockQuantity": 0
      },
      {
        "name": "XX杯麵。五香牛肉",
        "unit": "CUP",
        "stockQuantity": 0
      }
    ]
  }
}

3.png

參數 (Arguments)與變數(Variables)

我宜家好口渴,我淨是想知飲品清單,其他貨品我唔關心。但是Tec。士多完整售賣貨品清單咁長,好難找所以飲品。透過參數,例如,category(貨品種類),找出飲品清單方便簡潔。另外一個原因是一條query可以選擇出不同category既清單,不需要逐個category砌query。既可以慳development時間,又易管理。你唔是唔用下嗎?

query($categoryName:CATEGORY) {
  CommoditiesByCategory(category: $categoryName) {
    name
    category
    unit
    stockQuantity
  }
}

看返Server點定義CommoditiesByCategory呢。。。

Server-Side:SchemaClient-Side:Query
type Query{
...
CommoditiesByCategory(
category:CATEGORY)
:[Commodity]
}
query ($categoryName:CATEGORY){
CommoditiesByCategory(
category: $categoryName) {
name
category
unit
stockQuantity
}
}

是Schema,CommoditiesByCategory被定義需要提供一個參數,名叫"category"。 是Query,CommoditiesByCategory必需提供一個參數,名叫"category"。利用變數提供參數值,例如,$categoryName。所有變數數值必須經query傳入,再傳結對應的field。例如,$categoryName的值是經query再傳給CommoditiesByCategory field。

所有變數是用**$**開頭; 是GraphQL Playground左下角輸入變數值。

4.png

別名 (Alias)

樓上說到取得不同category清單,我地可唔可以是同一條query下取得不同category清單?試下用以下呢方法。

query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){
  CommoditiesByCategory(category: $categoryName1) {
    name
    category
    unit
    stockQuantity
  }
   CommoditiesByCategory(category: $categoryName2) {
    name
    category
    unit
    stockQuantity
  }
}

是會出error的。alias就可以解決問題。query被改成如下:

query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){
  getDrink:CommoditiesByCategory(category: $categoryName1) {
    name
    category
    unit
    stockQuantity
  },
   getSnack:CommoditiesByCategory(category: $categoryName2) {
    name
    category
    unit
    stockQuantity
  }
}

是每一個query加上一個alias,例如,getDrink、getSnack。返回結果會放是對應alias key name入面:

{
  "data": {
    "getDrink": [
      {
        "name": "240ml經典橙汁",
        "category": "DRINK",
        "unit": "CAN",
        "stockQuantity": 0
      }
    ],
    "getSnack": [
      {
        "name": "朱古力夾心餅",
        "category": "SNACK",
        "unit": "PACK",
        "stockQuantity": 0
      }
    ]
  }
}

片段 (Fragment)

大家有無覺得以下Query太冗長呢?有無方法簡化佢呢?有。Fragment幫到你!

query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){
  CommoditiesByCategory(category: $categoryName1) {
    name
    category
    unit
    stockQuantity
  }
   CommoditiesByCategory(category: $categoryName2) {
    name
    category
    unit
    stockQuantity
  }
}

是Playground入面,先定義好一個fragment如下:

fragment CommodityInfo on Commodity{
    name
    category
    unit
    stockQuantity
}

Fragment定義格式如下:

fragment <Fragment Name> on <Object Type>{
		The set of fields in <Object Type> above
}

定義一個fragment叫CommodityInfo。呢個CommodityInfo擁有Commodity物件部分的fields。query被改成如下:

query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){
  getDrink:CommoditiesByCategory(category: $categoryName1) {
    ...CommodityInfo
  },
   getSnack:CommoditiesByCategory(category: $categoryName2) {
    ...CommodityInfo
  }
}

fragment CommodityInfo on Commodity{
    name
    category
    unit
    stockQuantity
}

是CommoditiesByCategory當中的fields會被fragment所取代。"..."必須放是fragment前面。

5.png

操作名稱 (Operation Name)

如果唔需要一次過取得不同category清單,但是都你都想寫低query備用。如何用最簡潔方法編寫query呢?

query ($categoryName1:CATEGORY,$categoryName2:CATEGORY){
  getDrink:CommoditiesByCategory(category: $categoryName1) {
    ...CommodityInfo
  },
   getSnack:CommoditiesByCategory(category: $categoryName2) {
    ...CommodityInfo
  }
}

fragment CommodityInfo on Commodity{
    name
    category
    unit
    stockQuantity
}

答案是。。。用Operation Name, 例如,getDrinkOperation及getSnackOperation。同alias相似,今次只是放是query後面加上Operation Name;alias就是放是field name之前。

query getDrinkOperation($categoryName1:CATEGORY){
  CommoditiesByCategory(category: $categoryName1) {
    ...CommodityInfo
  }
}

query getSnackOperation($categoryName2:CATEGORY){
  CommoditiesByCategory(category: $categoryName2) {
    ...CommodityInfo
  }
}

fragment CommodityInfo on Commodity{
    name
    category
    unit
    stockQuantity
}

選擇那一個operation name,執行對應的query。

6a.png

右邊Panel出結果。

6b.png

進階操作

GraphQL不只訪問單層物件,它還可以訪問巢狀物件(nested objects)。舉個例子, 先是schema.ts先定義一個Store物件。因為每一間Tecky上堂地點都有一間Store賣零食,所以Store物件被定義記低每一個上堂地點(location)、每一個Commodity物件存貨量(每一款售賣貨品存貨量)。

type Store{
    id:ID!
    location:STORE_LOCATION!
    Commodities:[Commodity]
}

基於以上設計,所以Store物件Commodity物件有一個關連。如果你想知每一個上堂地點、每一款售賣貨品存貨量,你可以用一個nested objects query取得相關存貨量:

query{
  Stores{
    location
    Commodities{
      id
      name
      unit
      category
      stockQuantity
    }
  }
}

結果都會以nested objects形式表達:

{
  "data": {
    "Stores": [
      {
        "location": "TW_27F",
        "Commodities": [
          {
            "id": "0",
            "name": "240ml經典橙汁",
            "unit": "CAN",
            "category": "DRINK",
            "stockQuantity": 0
          },
          {
            "id": "1",
            "name": "朱古力夾心餅",
            "unit": "PACK",
            "category": "SNACK",
            "stockQuantity": 0
          },
          {
            "id": "2",
            "name": "XX杯麵。五香牛肉",
            "unit": "CUP",
            "category": "NOODLES",
            "stockQuantity": 0
          }
        ]
      }
    ]
  }
}

7.png

指令 (Directive)

之前Query/Mutation 都是以文字方法訪問結構資料,我們可否幫Query/Mutation加入功能達成某D工作,例如,檢查權限,只有Admin有權取得所有Stores的售賣貨品存貨量。

query($isAdmin:Boolean!){
  Stores @include(if:$isAdmin){//如果$isAdmin是true,query先可以取得所有Stores的售賣貨品存貨量。
    location
    Commodities{
      id
      name
      unit
      category
      stockQuantity
    }
  }
}

8.png

所有Directive是@開始,例如 @include,@skip。 例子 2 : 只想取得上堂地點清單:

query($getStoreLocationListOnly:Boolean!){
  Stores{
    location
    Commodities @skip(if:$getStoreLocationListOnly){//如果$getStoreLocationListOnly是true,就可以取得上堂地點清單
      id
      name
      unit
      category
      stockQuantity
    }
  }
}

9.png

Comments and Documentation

"""每一個上課地點的物件"""
type Store{
    id:ID!
    location:STORE_LOCATION!
    Commodities:[Commodity]
}

10.png

Developer要養成一個好習慣,寫comment同documentation。是GraphLQL加入「"""」就可以同時寫comment同documentation。

""" 我是comment! """

#點解呢?點解呢?看返Tec。士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 1 ~ 「Query 查詢/Mutation 變更 」Section

Mutation 變更


Mutation Statement用途是更新資料,可以做CRUD operations。做法是同Query Statement無異。如果要提供更新資料(參數,Arguments)太多,使用上不方便了,如以下例子:

mutation($name:String!,$unit:CATEGORY,$category:CATEGORY!,$stockQuantity:Int){
  addCommodity(name:$name,unit:$unit,category:$category,stockQuantity:$stockQuantity){
      ...CommodityInfo
  }
}

fragment CommodityInfo on Commodity{
    name
    category
    unit
    stockQuantity
}

Input Object Type可以提供一個方便方法「打包」好全部參數再交俾Mutation Statement。

輸入(Input Object Type)

請先是schema.ts定義一個Input Object Type如下:

input CommodityInput{
    name:String!
    unit:UNIT!
    category:CATEGORY!
    stockQuantity:Int = 0
}

Input Object Type格式如下:

input <Input Object Type Name>{
    The set of arguments in key-value pair
}

大家有無留意到stockQuantity有點特別?

stockQuantity:Int = 0

意思是提供一個預設值(default value)給某一個field。例如,給stockQuantity一個預設值0 。

在Playground打入以下Mutation Statement:

mutation($commodityInput:CommodityInput!){
  addCommodity(newCommodity:$commodityInput){
    ...CommodityInfo
  }
}

fragment CommodityInfo on Commodity{
    name
    category
    unit
    stockQuantity
}

是Query Variable@Playground提供$commodityInput變數資訊:

{
  "commodityInput":{
    "name":"Item#4",
    "unit":"CAN",
    "category":"DRINK",
    "stockQuantity":1
  }
}

執行訪問後就會取得最新加入的Commodity物件的資料。

{
  "data": {
    "addCommodity": {
      "name": "Item#4",
      "category": "DRINK",
      "unit": "CAN",
      "stockQuantity": 0
    }
  }
}

11.png

恭喜大家🥳! 大家已經成功了解如何寫Query/Mutation Statement及如何是Apollo GraphQL提供的Playground做測試。下一編就講解如何是React使用GraphQL。

Tec。士多Demo

References


JavaScript Everywhere: Building Cross-Platform Applications with Graphql, React, React Native, and Electron

留言

閱讀更多

Tec ‧ 士多開發日記系列:第一篇:GraphQL簡介

Tec ‧ 士多開發日記系列:第一篇:GraphQL簡介

Tec ‧ 士多開發日記系列:第一篇:GraphQL簡介
Andrew Shek
2021-05-21

話說Tecky入面有間「Tec ‧ 士多」,大家攞完零食飲品,放低錢入錢箱就可以了。因為小弟最近研究緊GraphQL,所以小弟就諗可唔可以用GraphQL開發一個網上版「Tec。士多」。


Tec ‧ 士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 1

Tec ‧ 士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 1

Tec ‧ 士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 1
Andrew Shek
2021-06-21

上一編Blog已經詳細講解GraphQL Frontend既實作方法。今編Blog就深入講解GraphQL Server運作模式及如何實作。開始講解GraphQL Server運作原理及實作之前,有三個概念一定要了解左先,模式(Schema)、解析器(Resolver)及資訊源(Data Sources)。


 Tec‧士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 2

Tec‧士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 2

 Tec‧士多開發日記系列:第二篇:實作GraphQL Server(基礎入門)- Part 2
Andrew Shek
2021-06-22

Part 1 講完理論部分。Part 2就開始實作了。


私隱政策網站地圖

© 2022 Tecky Academy Limited

hello@tecky.io
t.me/TeckyAcademy+852 9725 6400
商界展關懷 2019-2022
英國頒證機構 TQUK 認可中心
aws_partner
薯片叔叔共創社 重塑教育挑戰大獎