Skip to main content

學習 GraphQL 筆記(一)Object Type

Updated Markdown
Part of seriesGraphQL
學習 GraphQL 筆記(一)Object Type

最近因為公司的需求,可能要導入 GraphQL 到專案裡面,所以去做了些研究開始學習 GraphQL。

過程中實在是有許多地方的概念,很難搞懂(老了學不來了QQ)。在此紀錄自己學習後的理解,希望會幫助到入門的人,也希望有大大提點不正確的地方(拜託鞭我,我抖 M 不抖音)。

這邊先簡介一下 GraphQL,GraphQL 是由 Facebook 開源出來的專案。其特點是所見即所得的存取資料方式。通常在 RESTful 的 API 設計下,一個頁面可能需要多個 API 才能得到全部要呈現的資料,但是 GraphQL 只有一個 API,可以減少 HTTP 的來往次數。內建類型檢查的功能。個人使用至今,覺得缺點就是會增加 Backend 維護的複雜度。

以下我們可以從 GraphQL 官網的 Banner 看到使用的方式,先在 Backend 定義資料的格式,在前端建立想要的資料結構,最後回傳跟 query 一樣的資料結構 — 這就是所說的所見即所得。

GraphQL 範例GraphQL 範例

這邊必須抱怨一下,我看了許多介紹都沒提到 Resolver(尤其官網)。覺得缺少這塊會讓人學習上很大的缺陷。

接下來會分為三個部分來介紹:

  1. Object Type

  2. Query Schema

  3. Resolver

這篇主要講述 Object Type 的部分,但是還是會包含一些 Query Schema 的東西。以下是範例:

type Character {

  name: String!

  appearsIn: [Episode]!

  oldField: String @deprecated

  length(unit: LengthUnit = METER, sort: String): Float

}

extend type Character {

  age: String

}

Object Type & Field

Object Type 是 GraphQL 的基本元件,定義 Front End 可以從 Server 得到什麼類型的資料,以及其 Field。以上面為例:

  • type 是宣告了這是哪種類型的 Object Type

  • Character 宣告此 Object Type 的名稱,不能以 __為開頭

  • name 、 appearsIn 、 oldField、 length 則是其擁有的欄位,後面接的就是其欄位的資料型態

  • length 後面括號內則是參數

  • extend 是指在宣告完 Object Type 後,還可以再額外擴充欄位

Arguments

Field 的參數可以有多個,當參數是非必須的時候,則需要給定一個預設值。

birthday(year: Int = 1991): String

Operation Name

GraphQL 內有三個特殊的 Object Type — Query 、 Mutation、 Subscription,其使用上與一般的 Object Type 沒有差別,唯一的特殊點在於它們定義了 Entry Point。以上面 GraphQL 的 Banner 為例。第二個區域的 query 部分 project(name = "GraphQL") 就會需要在 Server 定義:

type Query {
  product(name: String): Product

}

裡面宣告查詢的接口名稱 product 和其參數 name: String,最後是回傳的 Object Type Product。

  • Query:獲取資料

  • Mutation:更新資料

  • Subscription:訂閱資料

Scalar

我們可以從上面的範例,看到 Field 後面會接上該 Field 資料的類型。在 GraphQL 裡面稱之為 Scalar,其內建有:

  • ID:接受 String 和 Int 格式,但最後都會轉成 String

  • String:UTF-8 字符,由 " 包裹

  • Int

  • Float

  • Boolean

除了內建的 Scalar 之外,GraphQL 也允許自定義 Scalar。其轉換的方式需要在 Resolver 設定

// Object Type
scalar Time

type Birthday {
  date: Time

}

type Query {
  birthday: Birthday

}


// resolver
const { GraphQLScalarType } = require("graphql");

const { Kind } = require('graphql/language');

Time: new GraphQLScalarType({

  name: 'Time',

  description: 'Date custom scalar type',

  parseValue: (value) => new Date(value),  // 接收

  serialize: (value) => new Date(value),   // 回傳

  parseLiteral: (ast) =>

    if (ast.kind === Kind.INT) return parseInt(ast.value, 10);

    return null;

});

Enumeration

一種特殊的 Scalar,限制 Field 的值在一個集合內。驗證值只能是集合中的某一個。

enum COLOR {
  RED
  GRAY
  BLACK
  WHITE
  ORANGE

}

type Cat {
  hair: COLOR

}

List & Non-Null

在 GraphQL 中預設 Field 的值可以為 null ,如果想要限定不能為 null 時,則在後面 scalar 加上 ! 。

List 則是讓資料回傳陣列的方式,方法是用 [] 將 Scalar 包裹起來,另外也可搭配 ! 使用。

Example 1:
myField: [String!]

myField: null // 有效

myField: [] // 有效

myField: ['a', 'b'] // 有效

myField: ['a', null, 'b'] // 錯誤

Example 2:
myField: [String]!

myField: null // 錯誤

myField: [] // 有效

myField: ['a', 'b'] // 有效

myField: ['a', null, 'b'] // 有效

最後將介紹 Interface 和 Union,這兩個概念對我來說是比較難懂的,到現在也不確定我的理解正不正確。希望有不同見解的人多多留言交流一下。

Interface & Union 在使用上常常會搭配 Variables 和 Inline Fragment 使用。因為 Interface & Union 可以相連多個 Object Type,但是最後只會回傳一個 Type,所以需要 Variables 來做篩選。Inline Fragment 則是用來獲得特定 Object Type 的資料。

Interface

Interface 是一個抽象類型,包含某些 Field。而 Object Type 必須包含這些 Field 才能實現這個 Interface。看完官網的這段話後,我真的在想說是在說三小。在看過其他的文章和在社群上討論後,個人理解 Interface 主要使用情境和功能在於,當不同的 Object Type 有許多相同或共用的 Field 時,可以使用 Interface 來做更好的管理。

interface Animal {
  weight: Float
  height: Float
  name: String

}

interface Bird {
  fly: Boolean

}

// Dog 裡面還是必須要重新宣告 Animal 裡面的 Fields,有點蠢但沒辦法,可以改進的空間
// 除了 Animal 裡面的 Fields,也能有自己額外的 Fields

type Dog implements Animal {
  weight: Float
  height: Float
  name: String
  master: String

}

// 一個 Object Type 可以實現複數個 interface

type Eagle implements Animal & Bird {
  weight: Float
  height: Float
  name: String
  fly: Boolean
  color: String

}

// 最後是回傳 interface

type Query {
  search(text: String!): Animal!

}

當在存取資料時,相同 interface 的欄位就可以直接 query 出來,但是額外的部分就需要使用 Inline Fragment 了

query {

  search(text: "Dog") {
    name
    weight
    height

    // Inline Fragment 的部分,下一篇會介紹
    ... on Dog {
      master

    }

  }

}

Union

Union 和 Interface 類似,但並不指定 Object Type 之間的任何共同欄位。所以資料都必須使用 Inline Fragment 來取得。適用於從單個 Field 回傳不相交的 Object Type。

union SearchResult = Photo | Person

type Person {

  name: String

  age: Int

}

type Photo {

  height: Int

  width: Int

}

type SearchQuery {

  firstSearchResult: SearchResult

}

// Query
# 無效查詢

{

  firstSearchResult {

    name

    height

  }

}

# 正確查詢

{

  firstSearchResult {

    ... on Person {

      name

    }

    ... on Photo {

      height

    }

  }

}

Input Type

這個 Object Type 需要搭配第二篇的 Variables 一起看可能會比較懂。在 Front End 發送請求的時候,有時候會需要傳入較為複雜的變數,而不僅僅只是個 Scalar 的時候,就可以在 Server 用 Input 來定義,可以使用的變數。

input Search {
  key: String!

}

# Front End Query
query SearchKey ($search: Search){
  search(key: $search)

}

# Variables
{
  key: "Popping"

}

About the author

  • I am not a programmer just a leaner. Writing JavaScript, Python, and Go and doing something on Kubernetes.

No comments yet. Be the first to comment.

More from this blog