深入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数量呈现爆 ...
随机推荐
- Python_Selenium 之以login_page为例实现对basepage封装好的方法调用和对common中公共方法的调用
目的:简化代码,提供框架该有的东西每一个函数 -提供了一个功能 - 公共的功能有了basepage,在PageObjects当中直接调用元素操作. 以下以login_page 为例,实现从配置文件中读 ...
- Java swing JFrame用repaint出现闪烁的问题解决
这几天用swing写登录页面背景动图的时候发现一直会有闪烁(我的类是继承JFrame),就来搜原因后发现好像是因为repaint会调用update()方法中的清屏操作导致闪烁. 我当时看的是这个文章 ...
- 关于switch语句的使用方法---正在苦学java代码的新手之菜鸟日记
输入月份与年份,判断所输入的月份有多少天. switch支持和不支持的类型 支持的类型 int 类型 short 类型 byte 类型 char 类型 enum (枚举)类型 (java5.0 之后支 ...
- 简单的Java面向对象程序
上一篇随笔Java静态方法和实例方法的区别以及this的用法,老师看了以后说我还是面向过程的编程,不是面向对象的编程,经过修改以后,整了一个面向对象的出来: /** * 3 延续任务2, 定义表示圆形 ...
- js正则中文
hi,大家好 今天跟小伙伴们浅谈以下如何用正则表示中文以及如何去运用.众所周知中文在计算机中是不能进行存储的.那我们是以什么办法让我们和计算机进行更好的沟通呢?常用的几种中文编码格式utf-8编码ut ...
- PTA题目集总结
PTA题目集1-3总结 一:前言 我认为题目集一的有八个题目,题量可能稍微有点多,但是题型较为简单,基本为入门题:题集二有五道题,题量适度,难度也适中:题集三虽然只有三道题,但是难度却骤然提升,前两题 ...
- webpack 快速入门 系列 - 自定义 wepack 上
其他章节请看: webpack 快速入门 系列 自定义 wepack 上 通过"初步认识webpack"和"实战一"这 2 篇文章,我们已经学习了 webpac ...
- 聊聊 Feign 的实现原理
What is Feign? Feign 是⼀个 HTTP 请求的轻量级客户端框架.通过 接口 + 注解的方式发起 HTTP 请求调用,面向接口编程,而不是像 Java 中通过封装 HTTP 请求报文 ...
- 《吃透微服务》 - 服务容错之Sentinel
大家好,我是小菜. 一个希望能够成为 吹着牛X谈架构 的男人!如果你也想成为我想成为的人,不然点个关注做个伴,让小菜不再孤单! 本文主要介绍 SpringCloud中Sentinel 如有需要,可以参 ...
- 详解 CDN 加速
背景 本来是为了深入了解 CDN 的,结果发现前置知识:IP.域名.DNS 都还不算特别熟,所以先写了他们 现在终于来聊一聊 CDN 啦 本文素材均出自:https://www.bilibili.co ...