GraphQL是什么?
因为我只写了查询和变更,所以其他内容请参考文献
(会花费时间,所以我会逐渐添加)
(可以先草稿,但可能会失去动力)
首先
这篇文章旨在尽可能简洁地叙述。如果您想了解更多详细信息,请查阅其他资料。
在这篇文章中,我们对数据进行了一些整理,以使其更易于阅读。
翻译:GraphQL 是什么
GraphQL是一种用于API的语言。它只定义了数据的格式,因此不依赖于语言或数据存储的方式(可以是数据库或文本)。
按照GraphQL的定义编写查询,并通过与服务器通信返回JSON。
查询和变更
GraphQL提供了各种关于数据获取和编辑的功能。
领域
最简单的是这个领域。
{
hero {
name
}
}
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
以相同形式的JSON返回所请求的查询。这对GraphQL来说很重要。(即可立即获取所需数据)
除了接受单一的String、int等类型,还可以接受Object类型作为参数。此外,注释以#开头表示。
{
hero {
name
# Queries can have comments!
friends {
name
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{ "name": "Luke Skywalker" },
{ "name": "Han Solo" },
{ "name": "Leia Organa" }
]
}
}
}
在上面的例子中,”friends” 将返回一个数组对象。
争论
只有通过上面的Field可以接收指定的数据。
因此,通过添加Arguments可以灵活地指定和获取各种数据。
{
human(id: "1000") {
name
height
}
}
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 1.72
}
}
}
过去常见的API设计中,REST只能通过URL查询等方式发送参数。然而,在GraphQL中,可以为每个字段等添加参数。
{
human(id: "1000") {
name
height(unit: cm)
}
}
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 172
}
}
}
该参数可以有各种不同的类型。您也可以自行声明。(请参考后面的模式和类型有关的信息)
别名
如果在同一查询中尝试引用hero(episode:EMPIRE)和hero(episode:JEDI),它们将成为相同的hero字段,无法引用。因此我们可以使用别名来解决这个问题。
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
通过使用别名,您可以同时接收两个结果。
片段
重复声明相同数据是冗余的,只需声明一次即可。
{
leftComparison: hero(episode: EMPIRE) {
name
appearsIn
friends {
name
}
}
rightComparison: hero(episode: JEDI) {
name
appearsIn
friends {
name
}
}
}
通过使用碎片,可以简单地声明上述示例如下。
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ],
"friends": [
{ "name": "Han Solo" },
{ "name": "Leia Organa" },
{ "name": "C-3PO" },
{ "name": "R2-D2" }
]
},
"rightComparison": {
"name": "R2-D2",
"appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ],
"friends": [
{ "name": "Luke Skywalker" },
{ "name": "Han Solo" },
{ "name": "Leia Organa" }
]
}
}
}
在获取相同格式的大量数据时非常方便易用。也可以使用多个片段。
变量
在大多数的应用程序中,需要在参数中指定的内容是会变化的。
在GraphQL中,不建议直接写变化的参数,因为GraphQL会在内部将其转换为特定的格式。
因此,建议使用变量。
首先,我们会像$变量名那样声明使用变量的查询语句。(第一行)
然后,我们会像过去一样书写使用该变量的查询语句。(第二行)
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
将变量名称描述为类似值的形式,并创建变量字典(通常为Json)。
{
"episode": "JEDI"
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{ "name": "Luke Skywalker" },
{ "name": "Han Solo" },
{ "name": "Leia Organa" }
]
}
}
}
在客户端,不需要创建新查询,只需要传递变量(Json)即可。
不应该使用变量来通过字符串拼接创建查询。
关于变量定义
变量定义类似于函数声明,变量定义以($episode: Episode)的方式声明,$作为变量名的前缀,后面是声明的类型。
在上述的例子中,$episode并非必需项。
要使其成为必需项,请声明为($episode: Episode!)。
(更详细的信息,请参考后文的模式和类型)
也可以声明默认值。
可以这样声明: ($episode: Episode = JEDI)。
当然,如果给定了变量(Json),将优先使用Json。
操作名称
在上面的例子中,我们写了query HeroNameAndFriends { … },但是按照以前的方式,我们也可以省略它们,并简单地写成{ … }。
通过编写这些代码,可以简化服务器和客户端之间的请求识别过程。
指示
通过使用变量,您可以更改查询的参数。但是,可能还需要根据变量来更改获取的数据,而不仅仅是参数。
query Hero($episode: Episode, $withAppearsIn: Boolean!, $withFriends: Boolean!) {
hero(episode: $episode) {
name
appearsIn @skip(if: $withAppearsIn)
friends @include(if: $withFriends) {
name
}
}
}
{
"episode": "JEDI",
"withAppearsIn": true,
"withFriends": true
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{ "name": "Luke Skywalker" },
{ "name": "Han Solo" },
{ "name": "Leia Organa" }
]
}
}
}
通过上面的例子,我们可以看出$with—-变量的变化。
如果是true,则会显示@include。如果是false,则会显示@skip。
突变
这次不仅涉及数据获取,还涉及编辑和添加功能。虽然GraphQL更加注重数据获取,但服务器端的数据编辑也是非常重要的。
在REST中,不推荐使用GET来修改数据,在GraphQL中也不推荐使用查询来修改数据。
在REST中通常使用POST、PUT等方法,但在GraphQL中建议使用mutations。
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
针对所请求的数据,返回更新后的数据作为响应。当您注意到createReview函数的review参数不是单一值而是对象时,请参考后续的模式(schema)和类型(type)。
尽管查询会并行执行,但更改操作会按顺序执行。
这意味着在一个请求中执行多个更改操作时,保证第一个更改操作先于第二个操作完成,并确保数据不会冲突。
内联片段
在GraphQL中,我们还可以利用接口来表示类型。(更多详情,请参考后面的模式和类型)
根据类型,您可以更改获取的数据。
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
{
"ep": "JEDI"
}
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
根据返回的Droid or Human(接口为Character)来展示与之相关的英雄。
特殊领域
通过将__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具有自己独特的类型概念。
对象类型和字段
GraphQL模式的基本组件是对象类型。
它表示从服务中获取的对象的类型和字段。
在模式中可以这样写。
type Character {
name: String!
appearsIn: [Episode]!
}
CharacterはGraphQLのオブジェクト型です. 複数のフィールドを持ちます. GraphQLのスキーマはほとんどこのオブジェクト型です.
nameとappearsInはCharacter型のフィールドです. これらはQueryから参照することが出来ます.
Stringは実装されたスカラー型の一つです. 詳しくはあとで.
String!はフィールドがnullでないことを保証します. つまり, このフィールドを参照すると常に値が返ってきます.
[Episode!]はEpisodeオブジェクトの配列を表します.またnullで無いので, 常に配列を返します.(配列の大きさが0を含む)
争论
在GraphQL的字段中可以添加参数。
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
在上述的例子中,我们定义了一个名为length的字段。
参数并不是必需的,可以设置默认值,在上述的例子中,默认值被设定为METER。
查询类型和变异类型
通常情况下,模式是对象类型的,但有两种特殊的模式。
schema {
query: Query
mutation: Mutation
}
所有的GraphQL服务都会有查询类型,并且可能还会有变更类型。
这些都被定义为查询的入口点。
例如
query {
hero {
name
}
droid(id: "2000") {
name
}
}
{
"data": {
"hero": {
"name": "R2-D2"
},
"droid": {
"name": "C-3PO"
}
}
}
如果执行类似的查询,则必须定义以下字段的Query类型:hero和droid。
type Query {
hero(episode: Episode): Character
droid(id: ID!): Droid
}
在Mutations中,也需要定义一个Mutation类型的字段。除了作为模式的入口点以外,它与对象类型相同,并且字段也会以相同的方式工作。
标量类型
在对象类型中,需要定义名称和字段,但必须在某个地方将这些字段解析成具体的数据。
这就是标量类型。GraphQL默认提供了以下一组标量类型。
当然可以自己定义标量类型。
scalar Date
对于将该类型进行序列化、反序列化和验证的操作取决于内部实现。
枚举类型
列举类型定义了具有确定值的元素,但是不允许在其中声明重复的值。
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
上面的例子定义了一个Episode枚举类型,并声明了NEWHOPE、EMPIRE和JEDI这三个值。
在各种不同语言的GraphQL实现中,可以使用语言中存在的枚举(Enum),对于像JavaScript这样没有枚举的语言可能需要自己实现,但这与客户端无关,作为GraphQL,枚举完全可以正常运作。
列表和非空
对象类型、标量类型和枚举类型是可以在GraphQL中定义的类型。但在实际使用时,除了这些类型,还可以应用额外的类型修饰符。
type Character {
name: String!
appearsIn: [Episode]!
}
在上述例子中,对String类型添加!,并将其设为非可空类型。
query searchWord($word: String!)
{
"word": null
}
如果按照上述方式声明并执行,将输出以下验证错误。
{
"errors": [
{
"message": "Variable \"$word\" of required type \"String!\" was not provided.",
"locations": [
{
"line": 1,
"column": 18
}
]
}
]
}
列表也可以像这样工作。例如,如果使用[String],它将返回一个String类型的数组。
另外,!和[]也可以组合使用。
fieldName: [String!]
假设, 列表本身可以是null, 但不能存在null元素.
可以采取以下对应措施。
如果定义一个非null非可为空类型的列表
fieldName: [String]!
会变成像这样。
界面
与其他语言一样,GraphQL也支持接口。接口是定义实现该接口所必须包含的字段的抽象类型。
例如,如果是Character接口,它将如下所示。
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
换言之,所有要实现Character的类型都必须实现它们。
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
除了Character接口中定义的字段,还可以声明类型特有的字段,如totalCredits、starships和primaryFunction。
接口很方便,但请小心使用方法。
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
primaryFunction
}
}
{
"ep": "JEDI"
}
{
"errors": [
{
"message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}
由于primaryFunction字段只在Droid类型中声明,所以会出现错误。
条件性片段
为了避免这个问题,您可以使用内联片段来仅获取Droid类型的内容。
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
请参考此处的内联片段。
联合类型
共用体和接口非常相似,但是不需要在每个共用体中定义相同的字段。
union SearchResult = Human | Droid | Starship
搜索结果将返回人类、机器人或者星际飞船中的任意一种。
共用体必須指定具体的的对象类型,不可以指定接口或其他共用体。
如果要引用上述的SearchResult共用体字段,请使用条件化的片段进行引用。
{
search(text: "an") {
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
{
"data": {
"search": [
{
"name": "Han Solo",
"height": 1.8
},
{
"name": "Leia Organa",
"height": 1.5
},
{
"name": "TIE Advanced x1",
"length": 9.2
}
]
}
}
输入类型
在之前的描述中,我们讲解了如何将标量类型(如enum和string)作为参数传递给字段。在GraphQL中,我们不仅可以传递标量类型,还可以简单地传递对象类型(尤其经常在mutations中使用)。虽然对象类型看起来与type类型类似,但使用的关键字不同,要用input关键字来表示。
input ReviewInput {
stars: Int!
commentary: String
}
请按以下方式使用
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
可以在input字段中指定input类型,但输出不一定会以相同的形式输出。
另外,input类型不能在字段中带有参数。
验证
还在中途
执行
还没到达中间。
反省
还没有完成
文献引用
GraphQL介绍