GraphQL.js 与服务端交互的新方式

1,595 阅读5分钟

GraphQL 官方解释为:一种用于 API 的查询语言

(文章不断完善中...我是写一点更新下)

前面有写过一篇文章《Next.js服务端渲染框架实战》中有提到会把数据请求与后端交互的方式从restful API方式切换成GraphQL方式。这篇文章将会尝试用GraphQL的方式与后端进行数据交互,先立个Flag,写个本地demo,实现基本的CURD(数据的增删改查)操作,欢迎大家一起交流讨论。

为啥要尝试GraphQL

从入门做前端以来,前台的数据展示由最初的混合开发模式,后端模板渲染静态页面,同时后端将从数据查询到的数据变量嵌入模板,实现了前端的数据展示。后来演变到前后端分离了,前端通过异步请求,restful API的方式请求后台接口拿到数据然后渲染到页面。页面的一些增删改查操作也是通过API接口的方式跟后端做交互。那问题就来了,日常开发的时候并不能保证提前定下的API接口字段百分百的准确,这就涉及到后续较大可能性要修改接口字段了。还有更奇葩的后端提供的接口是这样的。抛出一个接口,包含的数据层级特别深,你问他我要的字段在哪,回答是反正你要的字段在里面,自己找去。。。后台仁兄提前乐呵呵的下班了,你加班找来找去,顿时心中一万个XXX.当然如果后来越来樾规范,就不存在这种恶心的接口了。但接口字段变动是不太可能消除的,那有没有什么方法可以自己修改字段名称,修改数据结构,拿到自己想要的数据?哈哈,自己写后端接口算一个解决办法。当然还有更好的解决办法,就是今天的主角GraphQL了。

GraphQL优势

  • 请求你所要的数据。查询返回的数据是可预测的,因为控制数据的是应用(前端)而不是服务端
  • 获取多个资源,只用一个请求。自定义数据层次嵌套,减少数据冗余和太多的接口。
  • GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述。可以很好的组织后端放回到前台的数据结构

实践是检验真理的唯一标准

题外话:我自学的编程技术,野生程序员,刚入门的时候总是迷茫不知所措。看文档N遍还是不知所以然,后来尝试这按照教程去写代码,刚开始自己都知道写的是什么意思,但是写的过程中会遇到很多文档里没有提到的东西。逼迫着你去百度找到解决办法,那个时候也没人指导,身边页也没有搞程序开发的。自学起来真的很痛苦,后来发现学编程文档要看。更重要的是要实践,实际的写代码,会发现掌握的速度远比看文档要快。亲身体会建议大家,想学习更透彻,直接写代码吧。当然现在很多时候真的可以做到看文档就可以学会新技术了,那是因为有这几年的技术积累,不管是计算机概念还是原理都比刚开始入门时一穷二白时的基础好太多了,当你沉淀下来的时候你会发现,其实自己的知识体系超过了很多科班出生的程序员。最后:想深入理解技术原理,直接写代码吧!

正式开始: 技术栈很简单:Express+GraphQL

这里会展示一个基于 Express web服务器的一个 GraphQL API 服务端参考实现.首先新建一个文件夹express,在当前文件夹执行

npm install express express-graphql graphql faker --save 

在express 文件夹下新建 server.js文件,文件内容先丢出来,然后逐句解释说明

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

var faker = require('faker');

var randomName = faker.name.findName(); // Rowan Nikolaus
var randomEmail = faker.internet.email(); // Kassandra.Haley@erich.biz
var randomCard = faker.helpers.createCard(); // random contact card containing many properties

var {
    graphql,
    GraphQLSchema,
    GraphQLObjectType,
    GraphQLString,
    GraphQLInt,
    GraphQLList,
    GraphQLFloat,
    GraphQLID
}  = require('graphql');

const articleType = new GraphQLObjectType({
    name: 'Citys',
    fields: {
      time: { type: GraphQLString },
      title: { type: GraphQLString },
      order: { type: GraphQLInt}
    }
})
  
  
var Articleschema = new GraphQLSchema({
    query: new GraphQLObjectType({
      name: 'RootQueryType',
      fields: {
        id: {
            type: GraphQLID,
            description: '唯一id',
            args: {
                id: {  // 这里定义参数,包括参数类型和默认值
                    type: GraphQLID,
                    defaultValue: 1
                }
            },
            resolve : (_, args) => {
                console.log(_, args.id)
                return Number(1 + args.id)
            }
        },
        hashed:{
            type: GraphQLFloat,
            description: '随机值',
            resolve : () => {
                return Math.random()
            }
        },
        name: {
            type: GraphQLString,
            description: '姓名',
            resolve : () => {
                return randomName
            }
        },
        hello: {
          type: new GraphQLList(articleType),
          resolve() {
            return  [
                {'time':'2019-2-1', 'title':'武汉', 'order':1}, 
                {'time':'2019-2-2', 'title':'南昌', 'order':2},
                {'time':'2019-2-3', 'title':'贵阳', 'order':3},
            ]
          }
        },
        age: {
            type: GraphQLString,
            resolve: () => 18
        },
        list: {
            type: new GraphQLList(articleType),
            description: '城市信息数组',
            resolve: async (_, args) => {
                return await [
                    {'time':'2019-2-1', 'title':'武汉', 'order':1}, 
                    {'time':'2019-2-2', 'title':'南昌', 'order':2},
                    {'time':'2019-2-3', 'title':'贵阳', 'order':3},
                ]
            }
        },
      }
    })
});


var Commentschema = new GraphQLSchema({
    query: new GraphQLObjectType({
      name: 'RootQueryType',
      fields: {
        id: {
            type: GraphQLID,
            description: '唯一id',
            args: {
                id: {  // 这里定义参数,包括参数类型和默认值
                    type: GraphQLID,
                    defaultValue: 1
                }
            },
            resolve : (_, args) => {
                console.log(_, args.id)
                return Number(1 + args.id)
            }
        },
        hashed:{
            type: GraphQLFloat,
            description: '随机值',
            resolve : () => {
                return Math.random()
            }
        },
        name: {
            type: GraphQLString,
            description: '姓名',
            resolve : () => {
                return randomName
            }
        },
        hello: {
          type: new GraphQLList(articleType),
          resolve() {
            return  [
                {'time':'2019-2-1', 'title':'武汉', 'order':1}, 
                {'time':'2019-2-2', 'title':'南昌', 'order':2},
                {'time':'2019-2-3', 'title':'贵阳', 'order':3},
            ]
          }
        },
        age: {
            type: GraphQLString,
            resolve: () => 18
        },
        list: {
            type: new GraphQLList(articleType),
            description: '城市信息数组',
            resolve: async (_, args) => {
                return await [
                    {'time':'2019-2-1', 'title':'武汉', 'order':4}, 
                    {'time':'2019-2-2', 'title':'南昌', 'order':5},
                    {'time':'2019-2-3', 'title':'贵阳', 'order':6},
                ]
            }
        },
      }
    })
});

var app = express();

app.get('/', (req, res) => res.send('Welcome Graphql!'))

app.use('/graphql', graphqlHTTP({
  schema: Articleschema,
  graphiql: true,
}));


app.use('/comment', graphqlHTTP({
    schema: Commentschema,
    graphiql: true
}))

app.listen(5000, () => console.log('Now browse to localhost:4000/graphql'));

// # 每个字段都有对应的执行函数,也就是resolver函数

接下来修改package.json 加入npm 执行脚本。这里的nodemon 是node的工具,你也可以直接用node server.js.但是无法自动监听 server.js 文件修改后自动重启。nodemon 可以实现server.js 文件修改后自动重启服务。

{
  "scripts": {
    "dev":"nodemon server.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.7.1",
    "graphql": "^14.1.1"
  },
  "devDependencies": {
    "faker": "^4.1.0"
  }
}