对于GraphQL 的使用语法在上一节中已经大概介绍了基本的使用方式了,这一篇将会对上一篇入门作拓展,努力将全部的使用语法都覆盖到。前端
首先是介绍在前端查询时用的语法,分红Query 和Mutation 两部分,Subscription 的和Query 是相似的就不特别说明了。数据库
假设咱们如今有一个数据集,结构是这样的:segmentfault
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: "吴老师" } ] } ] } }
咱们经过上面的查询能够获得总共有两个学生,其中李老师同时教了他们两人。这是最最基本的用法。下面咱们慢慢加查询需求去改变结果。函数
咱们能够经过添加参数的方式限制返回集的内容post
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 的老师,因此李老师就给过滤掉了;由于设置了过滤只要名字中带“三”字的学生,因此李四也给过滤掉了。编码
同理,也能够用参数去对内容进行分页、跳过等操做,操做相同就不写例子了。url
由于在查询中,不一样的数据实体在Graphql 语句中自己是相似于直接请求这个单元的存在,因此若是你同时请求两个相同的集时它就会报错;由于它们都应该是一一对应的。这时你就能够用别名来解决这个问题:scala
query { san: student(name_contains: "三") { id name } wang: student(name_contains: "王") { id name } } # 结果: { data: { san: [ { id: 1, name: "张三" } ] wang: [ { id: 3, name: "王三" } ] } }
处理请求结果的时候要注意用了别名的内容是以别名为key 返回的,不是原来的名字了。翻译
看上面的查询语句,咱们能够看到当用了不一样的别名时咱们不免会产生这种写了一堆重复的字段名的状况。咱们这个例子字段少还好,但正常的业务要有个几十个字段都是挺常见的,这样写可就太费劲了,这时就能够祭出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: "王三" } ] } }
这个相对来讲是比较少用的,起码我我的的使用状况来看,我基本更倾向于“能省就省”的原则;但写教程的话就仍是介绍下吧。主要出如今同时有多个操做的状况下,用于区分操做数据。
query op1 { student(name_contains: "三") { id } } query op2 { student(name_contains: "王") { id } } # op1, op2 就是操做名。 # 但平常写query 你甚至能够将操做符("query")也省了像下面这样写就行 { student { id } }
这个参数有别于上面提到的Arguments, Arguments 是用于具体数据结点操做用的。Variables 指的是面向操做符时,可让Query 变得可复用,也方便在不一样地方使用。
假设咱们有两个不一样的页面,都要查询学生表但过滤不一样,这时若是咱们写两个查询像下面这样就很浪费,代码也很丑,也不能复用。
# 页面一用的 query { student(name_contains: "三") { id } } # 页面二用的 query { student(name_contains: "王") { id } }
由于本质上说,它们查询的内容是相同的,只是参数有点不同,这里咱们能够把参数给提取出来,经过在实际使用时再由不一样状况传参就好:
# 页面1、二都用同一个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.
注意定义参数有几个硬性规定:
# 这样若是没有给任何参数则$name 会默认等于“三” query($name: String = "三") { student(name_contains: $name) { id } }
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.
这个和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 } }
这里的使用情景主要是针对联合(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)就能够查不一样的东西但先以他们的类型做了过滤。
配合上面的例子食用,若是咱们没有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
是内置的,你能够在任何节点上查,它都会给你一个类型。
咱们知道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
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 }
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 去指定类型。
上面在那个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 }