前端er瞭解GraphQL,看這篇就夠了

image

GraphQL在近幾年被提到的次數愈來愈多,最近參加過的幾回技術大會前端分會場均提到過。對於這種光看名字並不容易想到它是什麼的東西,仍是存在些神祕感的。因而,打算去了解一下GraphQL究竟是什麼。php

什麼是GraphQL?

首先,GraphQL來自Facebook,若是你也跟我同樣徹底沒了解過它,不知道它究竟是幹什麼的,那麼你必定據說過另外一個叫作 Structured QL的東西。WHAT? 其實就是SQL了。前端

  • 嗯,和SQL同樣,GraphQL是一門查詢語言(Query Language
  • 一樣和SQL同樣的是,GraphQL也是一套規範,就像MySQLSQL的一套實現同樣,Apollo, Relay...也是GraphQL規範的實現
  • SQL不一樣的是,SQL的數據源是數據庫,而GraphQL的數據源能夠是各類各樣的REST API,能夠是各類服務/微服務,甚至能夠是數據庫

這裏借Apollo官網的一張圖來講明GraphQL所處的位置

這裏借Apollo官網的一張圖來講明GraphQL在互聯網應用架構中所處的位置python

幾個時間點

  • GraphQL規範於2015年開源
  • Subscriptions操做於2017年被加入到規範中

那麼爲何叫 Graph呢?

Graph是圖的意思,在GraphQL的世界裏,萬物皆爲圖,也就是說你能夠把你的業務模型建模爲圖。git

圖是將不少真實世界現象變成模型的強大工具,由於它們和咱們天然的心智模型和基本過程的口頭描述很類似。github

這裏就涉及到了圖論,Graph Database之類的知識,感興趣能夠某歌學習一下。數據庫

這樣咱們能夠對GraphQL大致有了一個概念了。那麼咱們來大概瞭解一下GraphQL。後端

GraphQL爲咱們解決了什麼問題呢?

簡單的說,站在前端角度,不少文章都會提到過爲了取代Restful API,稍微具體點:api

  • API字段的定製化,按需取字段
  • API的聚合,一次請求拿到全部的數據
  • 後端再也不須要維護接口的版本號了
  • 完備的類型校驗機制,提供了更健壯的接口
  • 。。。

在知道以上這些優勢是如何作到的以前,咱們先來對GraphQL作一個簡單的學習數組

前端學習GraphQL

作爲一名前端開發人員,只站在前端的角度上來講,更多的時候,咱們須要關心的只有兩個操做:緩存

  • query: 在GraphQL中這個關鍵字屬於schema(能夠理解爲協議)中的一種,表明你要執行的是查詢動做,即增刪改查中的查

  • mutation: 表明你要執行的動做是增刪改

querymutation 統稱爲schema。其實還有一個subscriptions在2017年被加入到規範(spec)中,讓咱們能夠更輕鬆的實現推送功能

這裏咱們以一個公司內部的分享平臺的兩個場景爲例,來介紹一下這兩個操做如何使用。

query操做

首先,最基礎的一個場景,分享平臺首頁須要調一個接口,獲取所有的分享列表,目前這個接口的調用方式是:

GET /api/share/allShares
複製代碼

返回

{
    "shares": [
        {
            "shareId": 1238272,
            "title": "分享一下Vue3.0",
            "desc": "Vue3.0就要發佈了,帶來了哪些新功能呢?",
            "where": "6F-19會議室",
            "startTime": 1548842400
        },
        {
            "shareId": 1238272,
            "title": "用flutter寫app頁面是一種什麼樣的體驗",
            "desc": "用跨平臺框架flutter來寫app頁面的初體驗", 
            "where": "6F-17會議室",
            "startTime": 1548842400
        },
        {
            "shareId": 1238272,
            "title": "Cordova原理",
            "desc": "一塊兒來了解一下Cordova",
            "where": "6F-19會議室",
            "startTime": 1548842400
        }
    ]
}
複製代碼

那麼換成GraphQL的方式,咱們能夠這麼寫

query {
	shares {
		title
		desc
		where
		startTime
	}
}
複製代碼

咦?發現漏掉了一個字段,若是咱們要跳至詳情頁,須要知道分享的id,改造一下

# 給一個查詢起一個名字是一個好習慣
query findAllShares {
	shares {
		# 爲id起了一個別名,叫shareId
		shareId: id
		title
		desc
		where
		startTime
	}
}
複製代碼

到此,一個基礎的查詢操做就完成了。

分頁

一般,若是列表類數據量比較大的話,咱們會採用分頁的方式獲取數據,而非一次性獲取所有數據,依然以剛纔的分享列表獲取爲例,若是用傳統的接口調用的方式,一般是要這樣去調接口:

GET /api/share/allShares?star=0&limit=10
複製代碼

返回

{
    "allShares": {
        "totalCount": 3,
        "shares": [
            {
                "shareId": 1238272,
                "title": "分享一下Vue3.0",
                "desc": "Vue3.0就要發佈了,帶來了哪些新功能呢?",
                "where": "6F-19會議室",
                "startTime": 1548842400
            },
            {
                "shareId": 1238273,
                "title": "用flutter寫app頁面是一種什麼樣的體驗",
                "desc": "用跨平臺框架flutter來寫app頁面的初體驗",
                "where": "6F-17會議室",
                "startTime": 1548842400
            },
            {
                "shareId": 1238274,
                "title": "Cordova原理",
                "desc": "一塊兒來了解一下Cordova",
                "where": "6F-19會議室",
                "startTime": 1548842400
            }
        ]
    }
}
複製代碼

咱們來繼續改造GraphQL的方式,分頁的方式:

# 分頁方式
query findAllShares($start: Int!, $limit: Int = 10) {
	allShares (start: $start, limit: $limit) {
		totalCount
		shares {
		    shareId: id
		    title
		    desc
		    where
	    	startTime
		}
	 }
}
複製代碼

GraphQL提供了完備的分頁解決方案,可參考 Pagination

下一場景,獲得了全部的分享列表,能夠進入詳情頁了。目前詳情頁有三個主要的查詢接口:獲取分享詳情,獲取分享的評論列表和獲取分享者全部的分享列表。若是是傳統的方式,咱們須要調三個接口:

// 獲取分享的詳情
GET /api/share/:shareId
複製代碼
// 分享詳情返回
{
    "shareDetail": {
        "shareId": 1238274,
        "title": "Cordova原理",
        "desc": "一塊兒來了解一下Cordova",
        "where": "6F-19會議室",
        "startTime": 1548842400,
        "attchments": "",
        "creatorId": 321,
        "lastUpdateTime": 1548842400,
        "logoUrl": "",
        ...
    }
}
複製代碼
// 獲取分享的評論列表
GET /api/share/comments/:shareId
複製代碼
// 分享評論列表返回
{
    "commentInfo": {
        "totalCount": 5,
        "comments": [
            {
                "id": 1,
                "content": "很是不錯",
                "userId": 213,
                "commentTime": 1548842400,
            },
            {
                "id": 2,
                "content": "很好",
                "userId": 214,
                "commentTime": 1548842400,
            },
            {
                "id": 3,
                "content": "不錯",
                "userId": 216,
                "commentTime": 1548842400,
            },
            {
                "id": 4,
                "content": "Very GOOD!",
                "userId": 2313,
                "commentTime": 1548842400,
            }
        ]
    }
}
複製代碼
// 分享的建立者的建立的所有分享列表
GET /api/share/shares/:creatorId
複製代碼
// 分享建立者的所有分享返回
{
    "hisShares": [
        {
            "shareId": 1238272,
            "title": "分享一下Vue3.0",
            "desc": "Vue3.0就要發佈了,帶來了哪些新功能呢?",
            "where": "6F-19會議室",
            "startTime": 1548842400
        },
        {
            "shareId": 1238273,
            "title": "用flutter寫app頁面是一種什麼樣的體驗",
            "desc":     "用跨平臺框架flutter來寫app頁面的初體驗", 
            "where": "6F-17會議室",
            "startTime": 1548842400
        },
        {
            "shareId": 1238274,
            "title": "Cordova原理",
            "desc": "一塊兒來了解一下Cordova",
            "where": "6F-19會議室",
            "startTime": 1548842400
        }
    ]
}
複製代碼

那麼若是用GraphQL的方式呢?

query shareDetailPage($shareId: Int!, $creatorId:ID!, $start: Int!, $limit: Int = 10) {
    # 分享詳情
    shareDetail: share (shareId: $shareId) {
        shareId: id
        title
        desc
        where
        logoUrl
        attchments
    }
    
    # 評論信息
    commentInfo(shareId: $shareId, start: $start, limit: $limit) {
        totalCount
        comments {
            id
            userId
            content
            commentTime
        }
    }
    
    # TA的分享
    hisShares (creatorId: $creatorId) {
        shares {
            title
            desc
            where
            startTime
        }
    }
}
複製代碼

一個查詢便可搞定。

mutation操做

變動操做,這裏只介紹一種場景。到了分享詳情頁,咱們可能會須要編輯這個分享,在傳統的方式中,須要調一個更新操做的接口:

POST /api/share/update/:shareId
FormData:
title=xxx&desc=xxx&where=xxx
複製代碼

調完此接口後爲了確認確實已經更新成功了,咱們可能還會調一次獲取分享詳情接口:

GET /api/share/:shareId
複製代碼

接下來咱們換成GraphQL的方式:

mutation editShareInfo($shareObj: ShareInput!) {
    editShareInfo(shareInfo: $shareObj) {
    shareId: id
    title
    desc
    where
    logoUrl
    attchments
  }
}
複製代碼

這樣,即可以直接將分享內容修改並返回修改後的分享詳情

其餘的功能

爲了咱們寫查詢語句部分代碼能有更好的可複用性,GraphQL還提供了Fragments(片斷), Inline Fragments(內聯片斷)和Directives(指令)功能。前二者能夠類比爲JavaScript中的function(函數)和anonymous function(匿名函數),Directives(指令)能夠根據咱們傳的參數來決定某些字段是否須要返回。這裏就不作過多介紹了。

以上的功能如何實現?

schema

經過上面的例子,確定會產生些疑問,咱們要如何知道能夠查詢哪些字段?使用哪些參數?這就須要引入schema了。

通俗點說,schema就是協議,規範,或者能夠當他是接口文檔。

GraphQL規定,每個schema有一個根(root)query和根(root)mutation。

咱們先來看Root Query怎麼寫,依然是上面的查詢的例子

# 定義一個根查詢
type Query {
    # 能夠查詢的字段和參數
    shares(start: Int = 0, limit: Int = 10, creatorId: ID): [Share!]!
    share(shareId: ID!): Share!
    commentInfo(shareId: ID!, start: Int = 0, limit: Int = 10): CommentInfo!
}
複製代碼

數據類型

若是你熟悉TypeScript或Flow的話可能會發現上面的寫法似曾相識,是的,裏面的含義就是你想的那樣。每個能夠查詢的字段的參數後面會跟標明這個參數的類型,!用來表示這個參數不能夠是空的。[]表示查詢這個字段返回的是數組,[]裏面是數組的類型。

上面咱們還看到了一些在TypeScript中不存在的類型,好比ID,ID咱們暫且把他當成字符串String類型就能夠了。相似咱們熟悉的JavaScrpit或TypeScript,GraphQL也有幾個基礎類型,在GraphQL中他們統稱叫標量類型(Scalar Type),主要包括:Int(整型), Float(浮點型), String(字符串), Boolean(布爾型)和ID(惟一標識符類型)。同時,GraphQL也容許你去自定義標量類型,例如:Date類型,只需實現相關的序列化,反序列化和驗證的功能便可。

對象類型

上面的根查詢定義中,咱們還看到了一些與業務相關的類型,好比Share, Comment,這些統稱爲對象類型。對象類型也是GraphQL中的schema的基本組件,他能夠告訴咱們在服務上能夠得到到哪些對象,以及這個對象有哪些字段。接下來咱們要作的就是定義這些對象類型,直到所有爲基礎類型。

# 定義Share的對象類型
type Share {
  id: ID!
  title: String!
  desc: String!
  startTime: Int!
  where: String
  attchments: String
  logoUrl: String
  creatorId: ID!
  lastUpdateTime: Int
  is_delete: Int
  score: Int
  createTime: Int!
}

# 定義評論信息對象類型
type CommentInfo {
  totalCount: Int!
  comments: [Comment!]!
}

# 定義評論對象類型
type Comment {
  id: ID!
  content: String!
  commentTime: Int!
  userId: ID!
  shareId: ID!
}

複製代碼

這樣,咱們就完成了schema的定義。

其餘類型和功能

GraphQL其實還有Enumeration types(枚舉類型),Union types(聯合類型)。同時,爲了代碼能更好的複用,GraphQL還提供了 Interface(接口)功能。這裏就不作過多介紹了。

實現執行

GraphQL約定,咱們須要爲Root Query(根查詢)和Root Mutation(根變動)裏面的每個字段提供一個resolver的函數。幷包裝成一個對象暴露出去,就像這樣:

const resolvers = {
    // 這裏面寫查詢操做字段的resolver函數
    Query: {},
    // 這裏面寫變動操做字段的resolver函數
    Mutation: {},
}

export default resolvers
複製代碼

讓咱們繼續寫完整:

// 一些加載數據的async function
import { loadSharesFromDB, loadShareById, loadCommentsByShareId } from './datasource'

const resolvers = {
    // 這裏面寫查詢操做字段的resolver函數
    Query: {
        shares: (parent, { start, limit, creatorId }, context, info) => {
            return loadSharesFromDB(start, limit, creatorId)
                    .then(...)
        },

        share: (parent, { shareId }, context, info) => {
            return loadShareById(shareId)
                    .then(...)
        },

        commentInfo: (parent, { shareId, start, limit }, context, info) => {
            return loadCommentsByShareId(shareId, start, limit)
                    .then(...)
        },
    },
    // 這裏面寫變動操做字段的resolver函數
    Mutation: {
         // ...
    },
}

複製代碼

一樣的,對於mutation(變動)操做,咱們也是先把schema完成:

# 定義Mutation根入口
type Mutation {
  editShareInfo(shareInfo: ShareInput!): Share! 
}

input ShareInput {
  id: ID!
  title: String!
  desc: String!
  where: String
}
複製代碼

而後,補全resolver函數:

import { updateShareInfo, loadShareById } from './datasource'

const resolvers = {
    Query: {
        // ...
    },
    
    Mutation: {
        editShareInfo: (parent, { shareInfo }, context, info) => {
            // 更新分享詳情,then獲取更新後的分享詳情
            return updateShareInfo(shareInfo.id, shareInfo)
                    .then(loadShareById(shareInfo.id))
        },  
    },
}

export default resolvers
複製代碼

到此,咱們就實現了這個簡單的GraphQL的Server了。

結語

GraphQL還有不少內容能夠探索,使用。好比,若是用Schema構建函數來生成對象類型,能夠標記某一個字段爲廢棄,並給出廢棄緣由。這樣,在版本迭代時,就能夠友好的提示到舊版本的使用者,促使其升級到最新的接口,經過某些檢測手段,咱們也能很輕鬆的知道舊版本的使用頻率,從而方便的讓咱們在某一個時間完全刪掉這個字段。

若是說GraphQL有什麼缺點,那可能就是上手確實沒那麼容易,並且對於後端同窗來講,仍是有不少坑要踩的,好比緩存,性能問題等。好在目前的GraphQL的資料已經不像幾年前那樣的匱乏,不論是官方仍是社區,GraphQL能夠參考的資源和解決方案都愈來愈多了。

無論怎樣,單純的對於前端er來講,若是說上一次前端的技術變革是SPA的普及的話,相信當下一次變革到來時,必定有GraphQL的影子。

一些連接
相關文章
相關標籤/搜索