①: 严格要求返回值
好比下面是后端返回的代码前端
{ name:1, name:2, name:3, }
前端想要的代码vue
{ name:1 }
从上面能够看出, name2 与 name3 其实我不想要, 那你传给我那么多数据干什么,单纯为了浪费带宽吗?可是吧。。也可理解为某些场景下确实很鸡肋,可是请求多了效果就厉害了。node
②: 设想一个场景, 我想要经过文章的id获取做者信息, 再经过做者信息里面的做者id请求他其余的做品列表, 那么我就须要两步才能完成, 可是若是这两个请求在服务端完成那么咱们只用写一个请求, 并且还不用找后端同窗写新接口。ios
③: 控制默认值: 好比一个做品列表的数组, 没有做品的时候后端很肯能给你返的是null
, 可是咱们更想保证一致性但愿返回[]
,这个时候能够用GraphQL进行规避.git
随便建个空白项目npm install graphql
.
入口路径以下src->index.jsgithub
var { graphql, buildSchema } = require('graphql'); // 1: 定义模板/映射, 有用mongoose操做数据库经验的同窗应该很好理解这里 var schema = buildSchema(` type Query { # 我是备注, 这里的备注都是单个井号; hello: String name: String } `); // 2: 数据源,能够是热热乎乎从mongodb里面取出来的数据 var root = { hello: () => 'Hello!', name:'金毛cc', age:5 }; // 3: 描述语句, 我要取什么样的数据, 我想要hello与name 两个字段的数据, 其余的不要给我 const query = '{ hello, name }' // 4: 把准备好的食材放入锅内, 模型->描述->整体的返回值 graphql(schema, query, root).then((response) => { console.log(JSON.stringify(response)); });
上面的代码直接 node就能够,结果以下: {"data":{"hello":"Hello!","name":"金毛cc"}}
;web
逐一攻克
1: buildSchema
创建数据模型mongodb
var schema = buildSchema( // 1. type: 指定类型的关键字 // 2. Query: 你能够理解为返回值的固定类型 // 3. 他并非json,他是graphql的语法, 必定要注意它没有用',' // 4. 返回两个值, 而且值为字符串类型, 注意: string小写会报错 ` type Query { hello: String name: String } `);
GraphQL 中内置有一些标量类型 String、 Int、 Float、 Boolean、 ID,这几种都叫scalar类型, 意思是单个类型
vuex
2: const query = '{ hello, name }'
作外层{}基本不变, hello的意思就是我要这一层的hello字段, 注意这里用','分割, 以后会把这个','优化掉.数据库
毕竟这样每次node
命令执行不方便, 而且结果出如今控制台里也很差看, 因此咱们要用一个专业工具'yoga'.
yarn add graphql-yoga
const { GraphQLServer } = require('graphql-yoga'); // 类型定义 增删改查 const typeDefs = ` type Query{ hello: String! #必定返回字符串 name: String id:ID! } ` const resolvers = { Query:{ hello(){ return '我是cc的主人' }, name(){ return '鲁鲁' }, id(){ return 9 }, } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(()=>{ console.log('启动成功, 默认是4000') })
固然最好用nodemon来启动文件 npm install nodemon -g
hello: String!
像这种加了个'!'就是必定有值的意思, 没值会报错.咱们返回data给前端,基本都会有多层, 那么定义多层就要有讲究了
const {GraphQLServer} = require('graphql-yoga'); const typeDefs = ` type Query{ me: User! # 这里把me这个key对应的value定义为User类型, 而且必须有值 } type User { # 首字母必须大写 name:String } ` const resolvers = { Query:{ me(){ return { id:9, name:'lulu' } } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(()=>{ console.log('启动成功, 默认是4000') })
当咱们取name
的值时:
定义起来会有些特殊
const { GraphQLServer } = require('graphql-yoga'); const typeDefs = ` type Query { # 返回是数组 arr:[Int!]! } ` const resolvers = { Query: { arr() { return [1, 2, 3, 4] } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('启动成功, 默认是4000') })
结果以下:
前端能够传参数,供给服务端函数的执行
)这个思路很神奇吧.const { GraphQLServer } = require('graphql-yoga'); const typeDefs = ` type Query{ greeting(name: String):String # 须要传参的地方, 必须在这里定义好 me: User! } type User { # 必须大写 name:String } ` const resolvers = { Query: { // 四个参数大有文章 greeting(parent, args, ctx, info) { return '默认值' + args.name }, me() { return { id: 9, name: 'lulu' } } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('启动成功, 默认是4000') })
greeting(name: String):String
greeting是key没的说, 他接收一个name参数为字符串类型, 这里必须指明参数名字, 返回值也必须是字符串类型, 也就是greeting是一个字符串.greeting(parent, args, ctx, info) {
这里咱们用到 args也就是参数的集合是个对象, 咱们args.name就能够取到name的值, 剩下的值后面用到会讲.由于左侧的参数是要放在url请求上的, 因此要用双引号;
const { GraphQLServer } = require('graphql-yoga'); const typeDefs = ` type Query{ lulu: User! } type User{ name:String age: Int chongwu: Chongwu! } type Chongwu{ name:String! age:Int } ` // 自定义的数据 const chongwuArr = { 1: { name: 'cc', age:8 }, 2: { name: '芒果', age:6 }, 9: { name: '芒果主人', age:24 } } const resolvers = { Query: { lulu() { return { name: '鲁路修', age: 24, chongwu: 9 } }, }, // 注意, 它是与Query并列的 User:{ // 1: parent指的就是 user, 经过他来获得具体的参数 chongwu(parent,args,ctx,info){ console.log('=======', parent.chongwu ) // 9 return chongwuArr[parent.chongwu] } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('启动成功, 默认是4000') })
这里数据量有点多, 我慢慢解析
效果以下:
Mutation
这个类里面, 相似vuex会要求咱们按照他的规范进行书写const { GraphQLServer } = require('graphql-yoga'); const typeDefs = ` type Query{ hello: String! } # 是操做而不是获取, 增删改:系列 type Mutation{ createUser(name:String!, age:Int!):CreateUser # 这里面能够继续书写create函数... } type CreateUser{ id:Int msg:String } ` const resolvers = { Query: { hello() { return '我是cc的主人' }, }, // query并列 Mutation: { createUser(parent, args, ctx, info) { const {name,age} = args; // 这里咱们拿到了参数, 那么就能够去awit 建立用户 return { msg:'建立成功', id:999 } } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('启动成功, 默认是4000') })
Mutation
是特殊类, 也是与Query
并列.Mutation
里面能够写多个函数, 由于他是个集合.效果以下: 接收id与提示信息
const { GraphQLServer } = require('graphql-yoga'); const typeDefs = ` type Query{ hello: String! } # 是操做而不是获取, 增删改:系列 type Mutation{ # 这个data随便叫的, 叫啥都行, 就是单独搞了个obj包裹起来而已, 不咋地 createUser(data: CreateUserInput):CreateUser } type CreateUser{ id:Int msg:String } # input 定义参数 input CreateUserInput{ # 里面的类型只能是基本类型 name: String! age:Int! } ` const resolvers = { Query: { hello() { return '我是cc的主人' }, }, // query并列 Mutation: { createUser(parent, args, ctx, info) { // **这里注意了, 这里就是data了, 而不是分撒开的了** const { data } = args; return { msg: '建立成功', id: 999 } } } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(() => { console.log('启动成功, 默认是4000') })
data
里面, 而后定义data的类效果与上面的没区别, 只是多包了层data如图:
这个只能说有利有弊吧, 多包了一层, 还多搞出一个类, 看似能够封装了实则'鸡肋啊鸡肋'
MutationType
特殊类型const {GraphQLServer} = require('graphql-yoga'); // 很是鸡肋, 这种事作不作, 该不应你作, 内心没点数 const typeDefs = ` type Query{ hello: MutationType } enum MutationType{ aaaa bbbb cccc } ` const resolvers = { Query:{ hello(){ // 只能返回菜单里面的内容, 这样能够保证不出格... p用 return 'bbbb' }, } } const server = new GraphQLServer({ typeDefs, resolvers }) server.start(()=>{ console.log('启动成功, 默认是4000') })
MutationType
的类, 限制只能用'aaa','bbb','ccc'中的一个字符串.先创建一个koa的工程
// 若果你没有koa的话, 建议你先去学koa, koa知识点比较少因此我暂时没写相应的文章.koa2 graphqlx
// main:工程名 不要与库重名npm install graphql koa-graphql koa-mount --save
大朗快把库安装好.
const Koa = require('koa') const app = new Koa() const views = require('koa-views') const json = require('koa-json') const onerror = require('koa-onerror') const bodyparser = require('koa-bodyparser') const logger = require('koa-logger') ////// 看这里 const mount = require('koa-mount'); const graphqlHTTP = require('koa-graphql'); const GraphQLSchema=require('./schema/default.js'); ////// const index = require('./routes/index') const users = require('./routes/users') // error handler onerror(app) // middlewares app.use(bodyparser({ enableTypes:['json', 'form', 'text'] })) app.use(json()) app.use(logger()) app.use(require('koa-static')(__dirname + '/public')) app.use(views(__dirname + '/views', { extension: 'pug' })) // 每个路径, 对应一个操做 app.use(mount('/graphql', graphqlHTTP({ schema: GraphQLSchema, graphiql: true // 这里能够关闭调试模式, 默认是false }))); // logger app.use(async (ctx, next) => { const start = new Date() await next() const ms = new Date() - start console.log(`${ctx.method} ${ctx.url} - ${ms}ms`) }) // routes app.use(index.routes(), index.allowedMethods()) app.use(users.routes(), users.allowedMethods()) // error-handling app.on('error', (err, ctx) => { console.error('server error', err, ctx) }); module.exports = app
这个画面是否是似曾相识!
const { GraphQLID, GraphQLInt, GraphQLList, GraphQLString, GraphQLSchema, GraphQLNonNull, GraphQLObjectType, GraphQLInputObjectType, } = require('graphql'); // id对应的详情 let idArr = { 1:{ name:'我是id1', age:'19' }, 2:{ name:'我是id2', age:'24' } } // 定义id的类 let GID= new GraphQLObjectType({ name: 'gid', fields: { name: { type: GraphQLString }, age: { type: GraphQLString }, } }) // 参数类型 不太对 let cs = new GraphQLInputObjectType({ name:'iddecanshu', fields: { id: { type: GraphQLString }, } }) //定义导航Schema类型 var GraphQLNav = new GraphQLObjectType({ name: 'nav', fields: { cc:{ // 传参 type:GraphQLString, // args:new GraphQLNonNull(cs), // 1; 这种是错的 args:{ data: { type:new GraphQLNonNull(cs), // 这种能够用data为载体了 } }, // args:{ // 3:这种最好用了。。。 // id:{ // type:GraphQLString // } // }, resolve(parent,args){ return '我传的是' + args.data.id } }, // greeting(name: String):String title: { type: GraphQLString }, url: { type: GraphQLString }, id: { // type:GraphQLList(GID), // 这里很容易忽略 type:GraphQLNonNull(GID), // 反复查找也没有专门obj的 这里用非空代替 async resolve(parent,args){ // console.log('wwwwwwwww', idArr[parent.id]) // 这个bug我tm。。。。。 // 须要是数组形式。。。。否则报错 // "Expected Iterable, but did not find one for field \"nav.id\".", // return [idArr[parent.id]]; // 2: 更改类型后就对了 return idArr[parent.id] || {} } }, } }) //定义根 var QueryRoot = new GraphQLObjectType({ name: "RootQueryType", fields: { navList: { type: GraphQLList(GraphQLNav), async resolve(parent, args) { var navList = [ { title: 'title1', url: 'url1', id:'1' }, { title: 'title2', url: 'url2', id:'2' } ] return navList; } } } }) //增长数据 const MutationRoot = new GraphQLObjectType({ name: "Mutation", fields: { addNav: { type: GraphQLNav, args: { title: { type: new GraphQLNonNull(GraphQLString) }, }, async resolve(parent, args) { return { msg: '插入成功' } } } } }) module.exports = new GraphQLSchema({ query: QueryRoot, mutation: MutationRoot });
GraphQLID
这种象征着单一类型的类单独拿到.const { GraphQLID, GraphQLInt, GraphQLList, GraphQLString, GraphQLSchema, GraphQLNonNull, GraphQLObjectType, GraphQLInputObjectType, } = require('graphql');
GraphQLObjectType
导出一个'type'let GID= new GraphQLObjectType({ name: 'gid', fields: { name: { type: GraphQLString }, age: { type: GraphQLString }, } })
GraphQLList
意思就是必须为数组var QueryRoot = new GraphQLObjectType({ name: "RootQueryType", fields: { navList: { type: GraphQLList(GraphQLNav), async resolve(parent, args) { var navList = [ { title: 'title1', url: 'url1', id:'1' }, { title: 'title2', url: 'url2', id:'2' } ] return navList; } } } })
let GID= new GraphQLObjectType({ name: 'gid', fields: { name: { type: GraphQLString }, age: { type: GraphQLString }, } }) var GraphQLNav = new GraphQLObjectType({ name: 'nav', fields: { cc:{ type:GraphQLString, args:{ data: type:new GraphQLNonNull(cs), // 这种能够用data为载体了 } }, resolve(parent,args){ return '我传的是' + args.data.id } }, id: { type:GraphQLNonNull(GID), async resolve(parent,args){ return idArr[parent.id] || {} } }, } })
实际效果如图所示:
这里咱们以vue为例import axios from "axios";
这个是前提query=
这个是关键点, 咱们之后的参数都要走这里
created(){ // 1: 查询列表 // ①: 必定要转码, 由于url上不要有{} 空格 axios .get( "/graphql?query=%7B%0A%20%20navList%20%7B%0A%20%20%20%20title%0A%20%20%20%20url%0A%20%20%7D%0A%7D%0A" ) .then(res => { console.log("返回值: 1", res.data); }); }
methods: { getQuery() { const res = ` { navList { title url id { name age } } }`; return encodeURI(res); }, }, created() { axios.get(`/graphql?query=${this.getQuery()}`).then(res => { console.log("返回值: 2", res.data); }); }
methods: { getQuery2(id) { const res = ` { navList { cc(data:{id:"${id}"}) title url id { name age } } }`; return encodeURI(res); } }, created() { axios.get(`/graphql?query=${this.getQuery2(1)}`).then(res => { console.log("返回值: 3", res.data); }); }
vue-apollo
技术栈是当前比较主流的, 可是这个库写的太碎了, 而且配置起来还要更改我原本的代码习惯.因此暂时没有找到我承认的库, 固然了本身暂时也并不想把时间放在开发这个插件上.
vue-apollo
提供了新的请求方式, 但是个人项目都是axios, 我就是不想换 凭什么要换说了这么多也是由于学习期间遇到了好多坑, 无论怎么样最后仍是可使用起来了, 在以后的使用过程当中仍是会不断的总结并记录, 遇到问题咱们能够一块儿讨论.但愿和你一块儿进步.