①: 嚴格要求返回值
好比下面是後端返回的代碼前端
{ 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, 我就是不想換 憑什麼要換說了這麼多也是由於學習期間遇到了好多坑, 無論怎麼樣最後仍是可使用起來了, 在以後的使用過程當中仍是會不斷的總結並記錄, 遇到問題咱們能夠一塊兒討論.但願和你一塊兒進步.