Graphql入门[toc]介绍GraphQL既是API的查询语言,也是满足你数据查询的运行时。怎么理解呢?GraphQL是作为通用REST架构的替代品而开发的。一般来说,它在架构上与REST属于同一层次。对于REST,GraphQL的优势在于:REST接口的数据由后端定义。如果返回了前端不希望的数据结构,则需要与后端通信,自行修改或适配;GraphQL向你的API发送一个GraphQL请求,客户端就可以准确获取到你想要的数据,不多也不少;GraphQL可以通过一次请求获取您的应用程序所需的所有数据,而RESTAPI需要请求多个URL;GraphQL查询的结构和结果非常相似,因此即使不知道服务器发生了什么,您也可以预测查询将返回什么结果。Facebook开源了GraphQL标准及其JavaScript实现。后来的主要编程语言也都实现了这个标准。HelloWorldconstKoa=require('koa');const{ApolloServer,gql}=require('apollo-server-koa');//定义一个查询consttypeDefs=gql`typeQuery{hello:String}`;//为上面定义的查询提供数据constresolvers={Query:{hello:()=>'Helloworld!',},};constserver=newApolloServer({typeDefs,resolvers});constapp=newKoa();server.applyMiddleware({app});app.listen({port:4000},()=>console.log(`?服务器准备好在http://localhost:4000${server.graphqlPath}`));标量类型在上面的例子中,我们为查询定义的返回参数是String,GraphQL支持的标量类型包括String、Int、Float、Boolean和ID。如果类型是列表,我们可以用这个[Int]来表示整数列表,如果是自定义类型也可以使用。同时,当它作为参数时,我们可以使用String!以指示该参数是必需的且非空的。指定查询字段案例数据异步Mock数据constKoa=require('koa');constlow=require('lowdb');constFileSync=require('lowdb/adapters/FileSync');const{ApolloServer,gql}=require('apollo-server-koa');constadapter=newFileSync('./db/data.json');constdb=low(adapter);//定义两个查询方法consttypeDefs=gql`typeQuery{hero:HeroheroList:[Hero]}typeHero{name:String,age:Int}`;//为各个方法提供数据constresolvers={Query:{hero:async(parent,args,上下文,信息)=>{returndb.get('hero').value()[0];},heroList:async(parent,args,context,info)=>{returndb.get('hero').value();},},};constserver=newApolloServer({typeDefs,resolvers});constapp=newKoa();server.applyMiddleware({app});app.listen({port:4000},()=>console.log(`?服务器准备好在http://localhost:4000${server.graphqlPath}`),);简单查询如果查询一个返回的对象,需要执行查询需要返回的字段,只需要hero的name属性一次即可返回指定字段Request,可以同时进行多个查询,比如同时查询hero和heroList。列表参数(Arguments)类似于RESTAPI。Graphql还支持为每个查询添加查询参数。我们只需要做以下修改:...consttypeDefs=gql`typeQuery{hero(id:Int):Hero}`;//为你的模式字段提供解析器函数constresolvers={Query:{hero:async(parent,args,context,info)=>{const{id}=args;返回db.get('hero').find({id}).value();}},};...下面演示查询结果:我们通过不同的查询ID查询不同的hreos:细心的你会发现一个细节,返回的字段名是first,second这里我使用了别名的方式,因为这两个hero字段相同,所以有冲突,我们用别名来区分。别名(Aliases)通过上图,我们接触到了别名的一个新概念。主要作用是让返回字段使用另一个名字,避免同一个方法不同参数下返回数据冲突的问题(如上)。只要在字段前面加上alias:,返回数据的key就可以自动替换成名字。碎片:如果你追求严谨的代码规范和工程能力,肯定会对first和second中多余的同名同龄感到不适应。一旦以后需要增加、删除或修改字段,就需要更多的空间,这样很容易造成bug。Graphql已经考虑到了这一点,我们可以使用分片来优化:通过分片,我们可以很好的提取出可用的单元,然后在需要的地方引入。变量(Variables)我们现在在查询,查询字符串中写的是变量,但实际上,我们的变量是多态的,每次都拼接查询字符串并不是一个明智的做法。Graphql提供了一个很好的方法来帮助我们解决这个问题。我们可以定义一个变量,用$variableName声明一个变量,用$variableName:value通过变量字典传递。如上图所示,我们通过变量的方式来解决变量多态性的问题。指令虽然变量可以帮助我们解决多态问题,但它们仍然不完美。比如有一个用户信息列表,我们想针对不同权限的人显示不同的信息。用户的收入只允许人力资源部门查看,用户的学历只允许直属领导查看等,所以我们需要针对不同的权限进行过滤。Graphql核心规范包含两个指令@include(if:Boolean)以仅在参数为真时包含此字段。@skip(if:Boolean)如果参数为真,则跳过该字段(不查询该字段)。通过上面的查询,我们对isAdmin变量的true和false的赋值进行了两次查询。从结果可以看出,我们已经控制了查询字段的显示。变更(Mutations)就像REST,我们不仅需要查询,还需要修改数据:操作过程如下TypeDefs用于在Mutation块中定义一个修改的方法,在Mutation块中使用解析器来实现。..consttypeDefs=gql`typeMutation{createHero(name:String,age:Int):[Hero]}typeHero{id:Intname:String,age:Int}`;constresolvers={Mutation:{createHero:async(parent,args,context,info)=>{constmodel=db.get('hero');constlen=model.value().length;model.push({id:len+1,...args}).write();返回db.get('英雄').value();}}};...最后,我们在变异中进行操作,如下图所示:从结果可以看出英雄孙长雪创建成功。请注意,在我们创建英雄之后,我们返回了heroList。和查询一样,我们也可以同时进行多个变更操作。不同之处在于查询是并行执行的,而更改操作是一个接一个地线性执行的。如下图所示:InputTypes(在前面的例子中,我们传入的类型都是标量类型,如果我们想传入一个复杂的结构数据,可以使用input关键字,其用法与type相同,并且服务端定义好之后,客户端查询的时候也可以使用这个类型。consttypeDefs=gql`typeQuery{hero(id:Int):Hero}##这里使用AttrInputtypetypeMutation{createHero(name:String,age:Int,attr:AttrInput):[Hero]}##这个定义输入参数##inputAttrInput{shoes:Stringclothes:Stringhat:String}typeHero{id:Intname:Stringage:Intattr:Attr}typeAttr{shoes:Stringclothes:Stringhat:String}`;然后我们进行更改操作:根据定义,我们的attr参数是:"attr":{"shoes":"Boot","hat":"Peaked","clothes":"shirt"}ObjectTypes(ObjectTypes)有时候我们不只是希望API返回的只是一个数字或者字符串,我们希望返回的是一个对象,而且是一个具有行为的对象。GraphQL可以完美满足这个需求。我们只需要将返回值设置为对象。constKoa=require('koa');const{ApolloServer,gql}=require('apollo-server-koa');//定义查询,返回值为对象consttypeDefs=gql`typeQuery{getDie(numSides:Int):RandomDie}typeRandomDie{numSides:内部!rollOnce:内部!roll(numRolls:Int!):[Int]}`;...//实现一个对象类RandomDie{constructor(numSides){this.边数=边数;}rollOnce(){return1+Math.floor(Math.random()*this.numSides);}roll({numRolls}){varoutput=[];对于(vari=0;i
