首頁 >web前端 >js教程 >理解 GraphQL:GraphQL 簡介

理解 GraphQL:GraphQL 簡介

王林
王林原創
2023-08-28 22:05:121449瀏覽

理解 GraphQL:GraphQL 简介

概述

GraphQL 是一個令人興奮的全新 API,用於即席查詢和操作。它非常靈活並提供許多好處。它特別適合公開以圖形和樹形式組織的資料。 Facebook 於 2012 年開發了 GraphQL,並於 2015 年開源。

它迅速發展並成為最熱門的技術之一。許多創新公司在生產中採用並使用了 GraphQL。在本教程中,您將學習:

  • GraphQL 的原理
  • 與 REST 相比如何
  • 如何設計架構
  • 如何設定 GraphQL 伺服器
  • 如何實作查詢與變更
  • 以及一些其他進階主題

GraphQL 的亮點在哪裡?

當您的資料按層次結構或圖形組織並且前端希望存取該層次結構或圖形的不同子集時,GraphQL 處於最佳狀態。考慮一個公開 NBA 的應用程式。你有球隊、球員、教練、冠軍,以及每一項的大量資訊。以下是一些範例查詢:

  • 金州勇士隊目前名單上的球員姓名是什麼?
  • 華盛頓巫師先發球員的姓名、身高和年齡是多少?
  • 哪位現役教練獲得的冠軍最多?
  • 教練在哪幾年為哪些球隊贏得了冠軍?
  • 哪位球員獲得 MVP 獎最多?

我可以提出數百個這樣的查詢。想像一下,您必須設計一個 API 將所有這些查詢公開給前端,並且當您的使用者或產品經理提出新的令人興奮的查詢內容時,能夠使用新的查詢類型輕鬆擴展 API。

這並不是一件小事。 GraphQL 旨在解決這個確切的問題,並且透過單一 API 端點,它提供了巨大的功能,您很快就會看到。

GraphQL 與 REST

在深入了解 GraphQL 的具體細節之前,我們先將其與 REST 進行比較,後者是目前最受歡迎的 Web API 類型。

REST 遵循資源導向的模型。如果我們的資源是球員、教練和球隊,那麼可能會有這樣的端點:

  • /玩家
  • /玩家/ 
  • /教練
  • /教練/ 
  • #/團隊
  • /團隊/

通常,沒有 id 的端點只傳回 id 列表,而具有 id 的端點則傳回一個資源的完整資訊。當然,您可以以其他方式設計 API(例如 /players 端點可能還會傳回每個玩家的姓名或有關每個玩家的所有資訊)。

在動態環境中,這種方法的問題在於,您要么獲取不足(例如,您只獲取 id 並且需要更多資訊),要么過度獲取(例如,在您只是對名字感興趣)。

這些都是難題。取得不足時,如果您取得 100 個 id,則需要執行 100 個單獨的 API 呼叫才能取得每個玩家的資訊。當過度獲取時,您會浪費大量後端時間和網路頻寬來準備和傳輸大量不必要的資料。

有多種方法可以透過 REST 來解決這個問題。您可以設計許多客製化端點,每個端點都準確地傳回您需要的資料。該解決方案不可擴展。要保持 API 的一致性是很困難的。很難進化它。很難記錄和使用它。當這些自訂端點之間存在大量重疊時,很難維護它。

考慮這些額外的端點:

  • /玩家/姓名
  • /players/names_and_championships
  • /團隊/先發

另一種方法是保留少量通用端點,但提供大量查詢參數。這個解決方案避免了多端點問題,但它違反了 REST 模型的原則,而且難以一致地發展和維護。

你可以說 GraphQL 已經將這個方法發揮到極限了。它不考慮明確定義的資源,而是考慮整個域的子圖。

GraphQL 類型系統

GraphQL 使用由類型和屬性組成的類型系統對域進行建模。每個屬性都有一個類型。屬性類型可以是 GraphQL 提供的基本類型之一,例如 ID、String 和 Boolean,也可以是使用者定義的類型。圖的節點是使用者定義的類型,邊是具有使用者定義類型的屬性。

例如,如果「玩家」類型具有「團隊」類型的「團隊」屬性,則表示每個玩家節點到團隊節點之間存在一條邊。所有類型都在描述 GraphQL 域物件模型的架構中定義。

這是 NBA 域的一個非常簡化的架構。球員有一個名字,與他最相關的球隊(是的,我知道球員有時會從一支球隊轉到另一支球隊),以及球員贏得的冠軍數量。

球隊有名字、球員陣容、球隊贏得的冠軍數。

type Player {
    id: ID
	name: String!
	team: Team!
	championshipCount: Integer!
}

type Team {
	id: ID
	name: String!
	players: [Player!]!
	championshipCount: Integer!
}

還有預先定義的入口點。它們是查詢、變更和訂閱。前端透過入口點與後端進行通信,並根據需要進行自訂。

這是一個簡單回傳所有玩家的查詢:

type Query {
    allPlayers: [Player!]!
}

感叹号表示该值不能为空。对于 allPlayers 查询,它可以返回空列表,但不能返回 null。另外,这意味着列表中不能有空玩家(因为它包含 Player!)。

设置 GraphQL 服务器

这是一个基于 Node-Express 的成熟 GraphQL 服务器。它有一个内存中的硬编码数据存储。通常,数据将位于数据库中或从其他服务获取。数据定义如下(如果您最喜欢的球队或球员未能入选,请提前致歉):

let data = {
  "allPlayers": {
    "1": {
      "id": "1",
      "name": "Stephen Curry",
      "championshipCount": 2,
      "teamId": "3"
    },
    "2": {
      "id": "2",
      "name": "Michael Jordan",
      "championshipCount": 6,
      "teamId": "1"
    },
    "3": {
      "id": "3",
      "name": "Scottie Pippen",
      "championshipCount": 6,
      "teamId": "1"
    },
    "4": {
      "id": "4",
      "name": "Magic Johnson",
      "championshipCount": 5,
      "teamId": "2"
    },
    "5": {
      "id": "5",
      "name": "Kobe Bryant",
      "championshipCount": 5,
      "teamId": "2"
    },
    "6": {
      "id": "6",
      "name": "Kevin Durant",
      "championshipCount": 1,
      "teamId": "3"
    }
  },

  "allTeams": {
    "1": {
      "id": "1",
      "name": "Chicago Bulls",
      "championshipCount": 6,
      "players": []
    },
    "2": {
      "id": "2",
      "name": "Los Angeles Lakers",
      "championshipCount": 16,
      "players": []
    },
    "3": {
      "id": "3",
      "name": "Golden State Warriors",
      "championshipCount": 5,
      "players": []
    }
  }
}

我使用的库是:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const app = express();
const { buildSchema } = require('graphql');
const _ = require('lodash/core');

这是构建架构的代码。请注意,我向 allPlayers 根查询添加了几个变量。

schema = buildSchema(`
  type Player {
    id: ID
    name: String!
    championshipCount: Int!
    team: Team!
  }
  
  type Team {
    id: ID
    name: String!
    championshipCount: Int!
    players: [Player!]!
  }
  
  type Query {
    allPlayers(offset: Int = 0, limit: Int = -1): [Player!]!
  }`

关键部分来了:连接查询并实际提供数据。 rootValue 对象可能包含多个根。

这里,只有 allPlayers。它从参数中提取偏移量和限制,对所有玩家数据进行切片,然后根据团队 ID 设置每个玩家的团队。这使得每个玩家都是一个嵌套对象。

rootValue = {
  allPlayers: (args) => {
    offset = args['offset']
    limit = args['limit']
    r = _.values(data["allPlayers"]).slice(offset)
    if (limit > -1) {
      r = r.slice(0, Math.min(limit, r.length))
    }
    _.forEach(r, (x) => {
      data.allPlayers[x.id].team   = data.allTeams[x.teamId]
    })
    return r
  },
}

最后,这是 graphql 端点,传递架构和根值对象:

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: rootValue,
  graphiql: true
}));

app.listen(3000);

module.exports = app;

graphiql 设置为 true 使我们能够使用出色的浏览器内 GraphQL IDE 来测试服务器。我强烈推荐它来尝试不同的查询。

使用 GraphQL 进行临时查询

万事俱备。让我们导航到 http://localhost:3000/graphql 并享受一些乐趣。

我们可以从简单的开始,只包含玩家姓名列表:

query justNames {
    allPlayers {
    name
  }
}

Output:

{
  "data": {
    "allPlayers": [
      {
        "name": "Stephen Curry"
      },
      {
        "name": "Michael Jordan"
      },
      {
        "name": "Scottie Pippen"
      },
      {
        "name": "Magic Johnson"
      },
      {
        "name": "Kobe Bryant"
      },
      {
        "name": "Kevin Durant"
      }
    ]
  }
}

好吧。我们这里有一些超级巨星。毫无疑问。让我们尝试一些更奇特的东西:从偏移量 4 开始,有 2 名玩家。对于每个球员,返回他们的名字和他们赢得的冠军数量以及他们的球队名称和球队赢得的冠军数量。

query twoPlayers {
    allPlayers(offset: 4, limit: 2) {
    name
    championshipCount
    team {
      name
      championshipCount
    }
  }
}

Output:

{
  "data": {
    "allPlayers": [
      {
        "name": "Kobe Bryant",
        "championshipCount": 5,
        "team": {
          "name": "Los Angeles Lakers",
          "championshipCount": 16
        }
      },
      {
        "name": "Kevin Durant",
        "championshipCount": 1,
        "team": {
          "name": "Golden State Warriors",
          "championshipCount": 5
        }
      }
    ]
  }
}

所以科比·布莱恩特随湖人队赢得了 5 个总冠军,湖人队总共获得了 16 个总冠军。凯文·杜兰特在勇士队只赢得了一次总冠军,而勇士队总共赢得了五次总冠军。

GraphQL 突变

魔术师约翰逊无疑是场上的魔术师。但如果没有他的朋友卡里姆·阿卜杜勒·贾巴尔,他不可能做到这一点。让我们将 Kareem 添加到我们的数据库中。我们可以定义 GraphQL 突变来执行从图表中添加、更新和删除数据等操作。

首先,让我们向架构添加突变类型。它看起来有点像函数签名:

type Mutation {
    createPlayer(name: String, 
                 championshipCount: Int, 
                 teamId: String): Player
}

然后,我们需要实现它并将其添加到根值中。该实现只是获取查询提供的参数并将一个新对象添加到 data['allPlayers']。它还确保正确设置团队。最后,它返回新的玩家。

  createPlayer: (args) => {
    id = (_.values(data['allPlayers']).length + 1).toString()
    args['id'] = id
    args['team'] = data['allTeams'][args['teamId']]
    data['allPlayers'][id] = args
    return data['allPlayers'][id]
  },

要实际添加 Kareem,我们可以调用突变并查询返回的玩家:

mutation addKareem {
  createPlayer(name: "Kareem Abdul-Jabbar", 
               championshipCount: 6, 
               teamId: "2") {
    name
    championshipCount
    team {
      name
    }
  }
}

Output:

{
  "data": {
    "createPlayer": {
      "name": "Kareem Abdul-Jabbar",
      "championshipCount": 6,
      "team": {
        "name": "Los Angeles Lakers"
      }
    }
  }
}

这是一个关于突变的黑暗小秘密......它们实际上与查询完全相同。您可以在查询中修改数据,并且可能只从突变中返回数据。 GraphQL 不会查看您的代码。查询和突变都可以接受参数并返回数据。它更像是语法糖,可以使您的架构更具人类可读性。

高级主题

订阅

订阅是 GraphQL 的另一个杀手级功能。通过订阅,客户端可以订阅每当服务器状态发生变化时就会触发的事件。订阅是后来引入的,不同的框架以不同的方式实现。

验证

GraphQL 将验证针对架构的每个查询或突变。当输入数据具有复杂形状时,这是一个巨大的胜利。您不必编写烦人且脆弱的验证代码。 GraphQL 将为您处理好它。

架构自省

您可以检查和查询当前架构本身。这为您提供了动态发现架构的元能力。下面是一个返回所有类型名称及其描述的查询:

query q {
  __schema {
    types {
      name
      description            
    }
  }

结论

GraphQL 是一项令人兴奋的新 API 技术,与 REST API 相比,它具有许多优势。它背后有一个充满活力的社区,更不用说Facebook了。我预测它将很快成为前端的主要内容。试一试。你会喜欢的。

以上是理解 GraphQL:GraphQL 簡介的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn