深入GraphQL 的使用语法
深入GraphQL 的使用语法
对于GraphQL 的使用语法在上一节中已经大概介绍了基本的使用方式了,这一篇将会对上一篇入门做拓展,努力将所有的使用语法都覆盖到。
1. 终端语法
首先是介绍在前端查询时用的语法,分成Query 和Mutation 两部分,Subscription 的和Query 是类似的就不特别说明了。
1.1 Query
假设我们现在有一个数据集,结构是这样的:
- 学生和老师各有各自的特有字段;
- 学生中有个获取所有相关老师的方法,老师中也有一个获取所有学生的方法;
- 学生和老师互为多对多关系;
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.
注意定义参数有几个硬性规定:
- 参数名要以 $ 开头。没得商量不以美元符开头它不认的。
- 参数的类型必须和它将会用到的地方的类型一样,否则会出错。因为Graphql 是静态类型的语言。
- 可以以类似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 的使用语法的更多相关文章
- 让ASP.NET Core支持GraphQL之-GraphQL的实现原理
众所周知RESTful API是目前最流行的软件架构风格之一,它主要用于客户端和服务器交互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制. RESTful的优越性是毋庸置疑 ...
- GraphQL
GraphQL 官方描述: GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时. GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地 ...
- sofa graphql 2 rest api 试用
大部分代码还是来自sofa 的官方文档,同时添加了docker && docker-compose集成 备注: 代码使用typescript 同时运行的时候为了方便直接运行使用ts ...
- GraphQL:一种不同于REST的接口风格
从去年开始,JS算是完全踏入ES6时代.在React相关项目中接触到了一些ES6的语法.这次接着GraphQL这种新型的接口风格,从后端的角度接触ES6. 这篇文章从ES6的特征讲起,打好语法基础:然 ...
- GraphQL入门有这一篇就足够了
GraphQL入门有这一篇就足够了:https://blog.csdn.net/qq_41882147/article/details/82966783 版权声明:本文为博主原创文章,遵循 CC 4. ...
- 10种JavaScript开发者必备的VS Code插件
摘要: 好的代码插件可以让工作效率翻倍,心情也更加舒畅! 原文:10 Must-have VS Code Extensions for JavaScript Developers 作者:Michael ...
- dinoql 使用graphql 语法查询javascript objects
dinoql 是一个不错的基于graphql 语法查询javascript objects 的工具包,包含以下特性 graphql 语法(很灵活) 安全的访问(当keys 不存在的时候,不会抛出运行时 ...
- ASP.NET Core中使用GraphQL - 第四章 GraphiQL
ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 ASP ...
- GraphQL基础篇
最近参与了一个大型项目,大型项目随着系统业务量的增大,不同的应用和系统共同使用着许多的服务接口API,而随着业务的变化和发展,不同的应用对相同资源的不同使用方法最终会导致需要维护的服务API数量呈现爆 ...
随机推荐
- Java中List和Map的区别
一.List和Map 1.特点 (1).List 1.可以允许重复的对象. 2.可以插入多个null元素. 3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序. 4.常用的实现类有 ...
- Jenkins 进阶篇 - 数据备份
随着我们的长期使用,Jenkins 系统中的内容会越来越多,特别是一些配置相关的东西,不能有任何丢失.这个时候我们就需要定期备份我们的 Jenkins 系统,避免一些误操作不小心删除了某些重要文件,J ...
- laya fgui 超简单的UI框架
FairyGUI 超简单的UI框架 Laya使用fgui的超简单UI框架 使用场景:用于使用fgui进行layaUI开发的程序人员 整个框架分为3个模块,共有4个类: FGUIManager :FGU ...
- Mysql 面试题(一网打尽,收藏版)
文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...
- NOIP模拟测试21「折纸·不等式」
折纸 题解 考试时无限接近正解,然而最终也只是接近而已了 考虑模拟会爆炸,拿手折纸条试一试,很简单 考你动手能力 代码 #include<bits/stdc++.h> using name ...
- 『心善渊』Selenium3.0基础 — 3、使用Selenium操作浏览器对象的基础API
目录 1.导入Selenium库 2.创建浏览器对象 3.浏览器窗口大小设置 4.浏览器位置设置 5.请求访问网址 6.浏览器页面前进.后退和刷新 7.关闭浏览器 相比于高大上的各种Selenium进 ...
- 在Excel中当遇到多个对象的目标值都不同时,如何快速设置条件格式突出未达标的对象
1.选择实际值的一个单元格,选择条件格式,新建规则,选择图中选项. 2.这里选择大于,然后选择对比的单元格.选择需要的格式确定.(因为要对比的目标值不同,所以需要给单元格去掉绝对引用,也就是$符号). ...
- Linux定时任务-cronie
1.cronie服务介绍 Linux crontab(cronie)是用来定期执行程序的命令. 当安装完成操作系统之后,默认就会启动此任务调度命令. crond 命令每分钟会定期检查是否有要执行的工作 ...
- 【Azure API 管理】解决调用REST API操作APIM(API Management)需要认证问题(Authentication failed, The 'Authorization' header is missing)
问题描述 在通过REST API的方式来管理APIM资源,需要调用Azure提供的management接口.而这所有的接口,都是需要有Token并且还需要正确的Token.如若不然,就会获取到如下的错 ...
- IE浏览器 AjaxForm文件上传错误:Stream ended unexpectedly
错误日志: 1 org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet requ ...