深入GraphQL 的使用语法

对于GraphQL 的使用语法在上一节中已经大概介绍了基本的使用方式了,这一篇将会对上一篇入门做拓展,努力将所有的使用语法都覆盖到。

1. 终端语法

首先是介绍在前端查询时用的语法,分成Query 和Mutation 两部分,Subscription 的和Query 是类似的就不特别说明了。

1.1 Query

假设我们现在有一个数据集,结构是这样的:

  1. 学生和老师各有各自的特有字段;
  2. 学生中有个获取所有相关老师的方法,老师中也有一个获取所有学生的方法;
  3. 学生和老师互为多对多关系;
classDiagram
Student <|-- Teacher2Student
Teacher <|-- Teacher2Student

class Student {
+Int id
+String name
+Int age
+teachers(id_eq: number) get_all_teachers
}
class Teacher {
+Int id
+String name
+Boolean gender
+students(name_contains: string) get_all_students
}
class Teacher2Student {
+Int id
+Int student_id
+Int teacher_id
+student() get_student
+teacher() get_teacher
}

首先最简单的使用方式我们可查:

query {
student {
id
name
teacher {
id
name
}
}
} # 结果: {
data: {
student: [
{
id: 1,
name: "张三",
teacher: [
{
id: 1,
name: "李老师"
},
{
id: 2,
name: "吴老师"
}
]
},
{
id: 2,
name: "李四",
teacher: [
{
id: 1,
name: "李老师"
}
]
},
{
id: 3,
name: "王三",
teacher: [
{
id: 2,
name: "吴老师"
}
]
}
]
}
}

我们通过上面的查询可以得到总共有两个学生,其中李老师同时教了他们两人。这是最最基本的用法。下面我们慢慢加查询需求去改变结果。

1.1.1 参数 Arguments

我们可以通过添加参数的方式限制返回集的内容

query {
student(name_contains: "三") { # <-- 这里限制了只要名字中包含了“三”的学生
id
name
teacher(id_eq: 2) { # <-- 这里限制了只要id=2 的老师
id
name
}
}
} # 结果: {
data: {
student: [
{
id: 1,
name: "张三",
teacher: [
{
id: 2,
name: "吴老师"
}
]
},
{
id: 3,
name: "王三",
teacher: [
{
id: 2,
name: "吴老师"
}
]
}
]
}
}

这时,因为我们过滤了只要id 为2 的老师,所以李老师就给过滤掉了;因为设置了过滤只要名字中带“三”字的学生,所以李四也给过滤掉了。

同理,也可以用参数去对内容进行分页、跳过等操作,操作相同就不写例子了。

1.1.2 别名 Aliases

因为在查询中,不同的数据实体在Graphql 语句中本身是类似于直接请求这个单元的存在,所以如果你同时请求两个相同的集时它就会报错;因为它们都应该是一一对应的。这时你就可以用别名来解决这个问题:

query {
san: student(name_contains: "三") {
id
name
}
wang: student(name_contains: "王") {
id
name
}
} # 结果: {
data: {
san: [
{
id: 1,
name: "张三"
}
]
wang: [
{
id: 3,
name: "王三"
}
]
}
}

处理请求结果的时候要注意用了别名的内容是以别名为key 返回的,不是原来的名字了。

1.1.3 片段 Fragments

看上面的查询语句,我们可以看到当用了不同的别名时我们难免会产生这种写了一堆重复的字段名的情况。我们这个例子字段少还好,但正常的业务要有个几十个字段都是挺常见的,这样写可就太费劲了,这时就可以祭出Fragments 来处理了:

fragment studentFields on Student {
id
name
} query {
san: student(name_contains: "三") {
...studentFields
}
wang: student(name_contains: "王") {
...studentFields
}
} # 结果: {
data: {
san: [
{
id: 1,
name: "张三"
}
]
wang: [
{
id: 3,
name: "王三"
}
]
}
}

1.1.4 操作名 Operation name

这个相对来说是比较少用的,起码我个人的使用情况来看,我基本更倾向于“能省就省”的原则;但写教程的话就还是介绍下吧。主要出现在同时有多个操作的情况下,用于区分操作数据。

query op1 {
student(name_contains: "三") {
id
}
} query op2 {
student(name_contains: "王") {
id
}
} # op1, op2 就是操作名。
# 但日常写query 你甚至可以将操作符("query")也省了像下面这样写就行 {
student {
id
}
}

1.1.5 操作参数 Variables

这个参数有别于上面提到的Arguments, Arguments 是用于具体数据结点操作用的。Variables 指的是面向操作符时,可以让Query 变得可复用,也方便在不同地方使用。

假设我们有两个不同的页面,都要查询学生表但过滤不同,这时如果我们写两个查询像下面这样就很浪费,代码也很丑,也不能复用。

# 页面一用的
query {
student(name_contains: "三") {
id
}
} # 页面二用的
query {
student(name_contains: "王") {
id
}
}

因为本质上说,它们查询的内容是相同的,只是参数有点不一样,这里我们可以把参数给提取出来,通过在实际使用时再由不同情况传参就好:

# 页面一、二都用同一个Query
query($name: String) {
student(name_contains: $name) {
id
}
}

使用时改变传进去的Variables, 例:

const query = gql`
query($name: String) {
student(name_contains: $name) {
id
}
}
` const page1 = post(URL, query=query, variables={name: "三"})
const page2 = post(URL, query=query, variables={name: "王"})

这样出来的结果就和上面写两个不同的Query 是一样的,但代码会优雅很多,Query 也得到了合理复用。如果有一天需要修改请求的返回结果,也不用跑到各个地方一个一个地修改请求的Query.

注意定义参数有几个硬性规定:

  1. 参数名要以 $ 开头。没得商量不以美元符开头它不认的。
  2. 参数的类型必须和它将会用到的地方的类型一样,否则会出错。因为Graphql 是静态类型的语言。
  3. 可以以类似TS 的方式给参数默认值,如下。
# 这样如果没有给任何参数则$name 会默认等于“三”
query($name: String = "三") {
student(name_contains: $name) {
id
}
}

1.1.6 指示符 Directives

Directives 可以翻译成指示符,但我觉得不太直观,它的功能主要是类似一个条件修饰,类似代码中的if-else 块差不多的功能。让你可以在外面指定要怎么请求的细节。

query($name: String, $withTeacher: Boolean!) {
student(name_contains: $name) {
id
teacher @include(if: $withTeacher) {
id
}
}
}

它的主要作用就是说,如果你在外面的variables 中给定withTeacher=true 那它就会请求teacher 节点,等同于:

query($name: String) {
student(name_contains: $name) {
id
teacher {
id
}
}
}

反之,如果指定withTeacher=false 那它就会省略teacher 节点,等同于:

query($name: String) {
student(name_contains: $name) {
id
}
}

Directives 主要有两个操作符:@include(if: Boolean)@skip(if: Boolean)

这两个的作用相反。另外Directives 这个功能需要服务端有相关支持才能用。但同时,如果需要服务端也可以自已实现完全自定义的Directives.

1.2 Mutation

1.2.1 操作参数 Variables

这个和Query 那边的规则完全一样,参见上面的内容即可,给个小例子:

# 无参写法
mutation create {
createStudent(name: "王五", age: 18) {
id
}
} # 有参写法
mutation create($name: String, $age: Int) {
createStudent(name: $name, age: $age) {
id
}
} # 另一种有参写法
# 假设createStudent 函数的参数的类型叫createStudentInput
mutation create($input: createStudentInput!) {
createStudent($input) {
id
}
}

1.2.2 行内片段 Inline Fragments

这里的使用情景主要是针对联合(Union) 类型的,类似于接口(interface) 与类(class)的关系。

假设我们有个接口叫动物(Animal), 有两个类分别是狗(Dog) 和鸟(Bird). 并且我们将这两个类由一个GraphQL 节点给出去:

{
animal {
name
kind
... on Dog {
breed
}
... on Bird {
wings
}
}
} # 结果 {
data: {
animal: [
{
name: "Pepe",
kind: "Dog",
breed: "Husky"
},
{
name: "Pipi",
kind: "Bird",
wings: 2
}
]
}
}

从上面的结果可以看出,它可以由不同的类型去查不同的“类”,但返回时可以合并返回。就类似于是从一个“接口” 上直接获取到实现类的数据了,非常具体。但大部分情况下我们可能不会合并着查两个不同结构的数据以一个数组返回,我们更多可能是用在于用同一个节点名(animal)就可以查不同的东西但先以他们的类型作了过滤。

1.2.3 元字段 Meta fields

配合上面的例子食用,如果我们没有kind 那个字段时,我们要怎么知道哪个元素是哪个类型呢?我们可以用元字段去知道我们当前操作的是哪个数据实体,主要的元字段有 __typename.

我们可以这样查:

{
animal {
name
__typename
... on Dog {
breed
}
... on Bird {
wings
}
}
} # 结果 {
data: {
animal: [
{
name: "Pepe",
__typename: "Animal__Dog",
breed: "Husky"
},
{
name: "Pipi",
__typename: "Animal__Bird",
wings: 2
}
]
}
}

__typename 是内置的,你可以在任何节点上查,它都会给你一个类型。

2. 类型定义

2.1 基础

我们知道GraphQL 是一个静态类型的语法系统,那么我们在真正使用前就必须先定义好它的类型。

GraphQL 的类型定义叫做Schemas. 有它自已独立的语法。里面有各个基本类型Scalar,可以定义成不同对象的Type. 也可以自已用基本类型定义成新的类型。

所有的不同的对象最终会组成一个树状的结构,根由schema 组成:

schema {
query: Query
mutation: Mutation
}

然后再定义里面一层一层的子对象,比如我们上面那个模型大概可以写成:

type Query {
student(name_contains: String): Student
teacher(id_eq: ID): Teacher
} type Student {
id: ID!
name: String!
age: Int
teachers: [Teacher!]!
} type Teacher {
id: ID!
name: String!
gender: Boolean
}

像上面这样我们就定义了两个不同的对象及他们的属性。其中,如果是必填或者说非空的字段则带有"!" 在它的类型后面,比如id: ID! 就表明id 是个非空的字段。非空的字段如果在操作中给它传null 会报错。另外某种类型组成的数组可以用类型加中括号组成,比如上面的Student 里面的Teacher.

定义一个字段为数组:

myField: [String!]

这样定义呢,表明了这个字段本身是可以为 null 的,但它不能有 null 的成员。比如说:

const myField: null // valid
const myField: [] // valid
const myField: ['a', 'b'] // valid
const myField: ['a', null, 'b'] // error

但如果,是这样定义的:

myField: [String]!

则代表它本身不能为 null 但它的组成成员中可以包含 null .

const myField: null // error
const myField: [] // valid
const myField: ['a', 'b'] // valid
const myField: ['a', null, 'b'] // valid

2.2 自带类型

GraphQL 默认的自带类型只有5 种。分别是:

ID: 就类似传统数据库中的ID 字段,主要用于区别不同的对象。可以直接是一个Int, 也可能是一个编码过的唯一值,比如常见的relay 中使用的是“类名:ID” 的字符串再经base64 转码后的结果作为ID. 要注意的是这个ID 只是存在于Graphql 中的。它不一定和数据库中的是对应的。比如relay 这个情况,数据库中存的可能还是一个整数并不是那个字符串。

Int: 整数,可正负。

Float: 双精度浮点数,可正负。

String: UTF-8 字符的字符串。

Boolean: true / false.

如果不能满足你的业务场景你就可以自定义新的类型,或者是找第三方做好的拓展类型。

定义一个类型的Graphql 写法很简单,比如我们新增一个Date 类型。

scalar Date

就这样就可以了,但是你还需要在你的代码中实现它的具体功能,怎么转换出入运行时等等。

另外,Graphql 中支持枚举类型,可以这样定义:

enum GenderTypes {
MALE
FEMALE
OTHERS
}

2.3 接口(Interface) 和联合类型(Union)

Interface 和 Union 很像,所以我就合在一起讲了。

Interface 和其他语言的类似,都是为了给一个通用的父类型定义用的。可以像这样定义及使用:

interface Animal {
id: ID!
name: String
} type Dog implements Animal {
id: ID!
name: String
breed: String
} type Cat implements Animal {
id: ID!
name: String
color: String
}

可以看到,接口定义的每个字段在实现时都会带上,但它也可以有自已的字段。查询时,需要注意的是:你不可以直接在Animal 上查到各个独有的字段,因为当你在Animal 上做查询时系统并不知道你当前查询的对象是Dog 还是Cat. 你需要用inline fragment 去指定。

# 这样查直接报错:
# "Cannot query field \"color\" on type \"Animal\". Did you mean to use an inline fragment on \"Cat\"?"
query {
animal {
id
name
color
}
} # 正确的打开方式:
query {
animal {
id
name
... on Cat {
color
}
}
}

讲完Interface, 我们再看看Union.

Union 你可以直接理解成是没有共同字段的Interface.

union Plant = Lily | Rose | Daisy

查询时和接口一样得用inline fragments 去指定类型。

2.4 输入类型 Input types

上面在那个Mutation 的Variables 举例子时稍微提到过,就是给某个操作的输入的所有参数指定成一个类型,这样可以更方便地添加内容也增加了代码可复用的程度。

假设我们有一个Mutation 的定义是这样的:

type Mutaion {
createSomething(foo: Int, bar: Float): Something
}

使用Input types:

input CreateSomethingInput {
foo: Int
bar: Float
} type Mutaion {
createSomething(input: CreateSomethingInput): Something
}

深入GraphQL 的使用语法的更多相关文章

  1. 让ASP.NET Core支持GraphQL之-GraphQL的实现原理

    众所周知RESTful API是目前最流行的软件架构风格之一,它主要用于客户端和服务器交互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制. RESTful的优越性是毋庸置疑 ...

  2. GraphQL

    GraphQL 官方描述: GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时. GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地 ...

  3. sofa graphql 2 rest api 试用

      大部分代码还是来自sofa 的官方文档,同时添加了docker && docker-compose集成 备注: 代码使用typescript 同时运行的时候为了方便直接运行使用ts ...

  4. GraphQL:一种不同于REST的接口风格

    从去年开始,JS算是完全踏入ES6时代.在React相关项目中接触到了一些ES6的语法.这次接着GraphQL这种新型的接口风格,从后端的角度接触ES6. 这篇文章从ES6的特征讲起,打好语法基础:然 ...

  5. GraphQL入门有这一篇就足够了

    GraphQL入门有这一篇就足够了:https://blog.csdn.net/qq_41882147/article/details/82966783 版权声明:本文为博主原创文章,遵循 CC 4. ...

  6. 10种JavaScript开发者必备的VS Code插件

    摘要: 好的代码插件可以让工作效率翻倍,心情也更加舒畅! 原文:10 Must-have VS Code Extensions for JavaScript Developers 作者:Michael ...

  7. dinoql 使用graphql 语法查询javascript objects

    dinoql 是一个不错的基于graphql 语法查询javascript objects 的工具包,包含以下特性 graphql 语法(很灵活) 安全的访问(当keys 不存在的时候,不会抛出运行时 ...

  8. ASP.NET Core中使用GraphQL - 第四章 GraphiQL

    ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...

  9. GraphQL基础篇

    最近参与了一个大型项目,大型项目随着系统业务量的增大,不同的应用和系统共同使用着许多的服务接口API,而随着业务的变化和发展,不同的应用对相同资源的不同使用方法最终会导致需要维护的服务API数量呈现爆 ...

随机推荐

  1. wrk(1)- 详细使用

    介绍 wrk 是一个类似 ab(apache bench).jmeter 的压力测试工具,官方称它为:现代的 HTTP 基准测试工具 用 C 编写的 HTTP 协议压测工具 底层基于 epoll 和 ...

  2. 七、Nginx反向代理

    调度器调度后端服务器 : web高可用  负载均衡   解决web单点故障 部署后端服务器---配置Nginx服务器(定义集群.请求转发)---起服务.测试----配置集群池属性(权重.失败次数.失败 ...

  3. sync.waitgroup ----等待goroutine的执行完成

    可以尝试改变wg.add里的值,改变wg.wait,或者wg.done的出现次数以及位置. 感受它的使用

  4. .NET 6 亮点之工作负载,它是统一 .NET 的基础

    随着.NET 6 Preview 5的发布,大家认真的看相关文章或者是动手做一个MAUI示例的时候就会碰到一个新概念工作负载(workload),相关规范参见 https://github.com/d ...

  5. 让Github畅通无阻,FastGithub1.0.0发布

    前言 我近半年来被github的抽风虐得没脾气了,虽然我有代理的方式来上网,但代理速度并不理想,而且有时代理服务一起跟着抽风.这时候,我会搜索"github访问不了"相关题材,其中 ...

  6. 【题解】Luogu p3478 [POI2008]STA-Station 动态规划

    题目描述 给出一个$N$个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大 输入输出格式 输入格式 第一行一个数$n$,表示树上共有$n$个点接下来$n-1$行,表示$n-1$条边;每行 ...

  7. NOIP模拟测试10「大佬·辣鸡·模板」

    大佬 显然假期望 我奇思妙想出了一个式子$f[i]=f[i-1]+\sum\limits_{j=1}^{j<=m} C_{k \times j}^{k}\times w[j]$ 然后一想不对得容 ...

  8. c 语言学习第一天

    编辑器:Dev-C++ 变量命名(标识符) 变量名只能是英文字母[A-Z,a-z]和数字[0-9]或者下划线[_]组成. 第一个字母必须是字母或者下划线开头. 变量名区分大小写.例如:Fish≠fis ...

  9. 12、关于系统cpu的计算

    1.cpu核数和逻辑cpu: CPU总核数 = 物理CPU个数 * 每颗物理CPU的核数: 总逻辑CPU数 = 物理CPU个数 * 每颗物理CPU的核数 * 超线程数 2.查看linux的cpu相关信 ...

  10. 面试系列——Mysql索引

    1.索引分类 Hash索引Hash 索引查询效率很高,时间复杂度O(1).Mysql Innodb引擎不支持hash索引的.Hash索引适合精确查找,不适合范围查找. 平衡二叉树时间复杂度为 O(n) ...