置顶 什么是GraphQL ?

技术 javascript demo

Node

2021-03-17 18:11:49

88

作者:黑夜男神

什么是GraphQL ?

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

背景

从 2011 开始,Facebook 开始越来越重要移动端,一支很小的团队开始去做 Android 与 iOS 应用,Facebook开发GraphQL的最初原因是移动用户的增加、低功耗设备和松散的网络。GraphQL最小化了需要网络传输的数据量,从而极大地改善了在这些条件下运行的应用程序。并于 2015 年开源

优点

  1. 需要什么就获取什么数据不多不少
  2. API无需定义各种路由
  3. 无需管理API版本 兼容多个平台
  4. 获取多个资源只用一个请求避免 数据冗余和请求冗余
  5. 接口变动 同步更新接口文档 无需手动维护

GraphQL 对比 REST API

在了解grahql 之前 想必我们对restful API 应该不会太陌生吧,在此先了解 一般来说后端 开发应用程序的都会使用MVC这种比较经典的模式经典MVC模式中

  1. M是指业务模型
  2. V是指用户界面
  3. C则是控制器
  • 使用MVC的目的是将M和V的实现代码分离,不过在目前前后端开发分离模式下基本去掉后端V这一层了吧所以现在我们在开发一个接口的时候 基本就是route就是我们接口地址 controller 实现的逻辑,一般是参数校验、权限控制、调用service以及返回结果。service中则是业务的具体实现,比如操作数据库等。这样就完成了我们的rest而API。REST架构的设计范式侧重于分配 HTTP 请求方法(GET、POST、PUT、PATCH、DELETE)和 URL 端点之间的关系 image

  • graphql在这里做了一些什么呢,它相当于在C层上又加了一层过滤,我们可以将多个RESTFUL请求 把需要用的数据源进行合并聚合,然后通过一个graphql进行查询。而graphql 它只需要 get 和post 2种方式

  • graphql它是数据库无关的,而且可以在使用API的任何环境中有效使用,我们可以理解为GraphQL是基于API之上的一层封装

  • GraphQL对数据支持的操作有

    • 查询(Query):获取数据的基本查询。
    • 变更(Mutation):支持对数据的增删改等操作。
    • 订阅(Subscription):用于监听数据变动、并靠websocket等协议推送变动的消息给对方。

image

查询语法

业务场景

在ky 创意链接列表数据 分成2分接口进行渲染 https://mbeta.koyye.com/theme-Women-t-2616.html?bid=3416 在渲染这个列表的时候 因为业务需要在列表里面加6个banner位 原本说让后端那边在同一个接口返回到前端渲染 但是后端大佬说 因为banner的数据 不是列表不是同一类型的数据,所以需要重新开一个接口来进行查询 所以,在前端渲染这个列表的时候需要同时去请求2个接口 这样增多http请求 如果在此业务场景使用grahql 只要一次请求 就可以拿到想要的数据了。

DEMO 演示

  • 为了学习graphql 这里我用vue 做一个 TODOLIST 来体验一下 image

    前端部分

    我用vue-cli3 快速搭建一个简单vue的应用这样也是方便整体了解在实际项目中前端是如何配置的 安装最新的vue-cli3 安装方式 可以根据自己包管理工具进行安装 我这边用的yarn

    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli
  • 安装好后用vue- cli初始化一个项目 这2个都行 根据自己配置选择安装

    vue create vuecli3test
    # OR
    vue ui 
  • 项目初始化完后 安装 我们需要用的graphql的一些依赖包 为了界面美观一点 我顺便装了下 ElementUI

    "dependencies": {
      "apollo-cache-inmemory": "^1.6.6",
      "apollo-client": "^2.6.10",
      "apollo-link": "^1.2.14",
      "apollo-link-http": "^1.5.17",
      "core-js": "^3.6.5",
      "element-ui": "^2.14.1",
      "graphql": "^15.4.0",
      "graphql-tag": "^2.11.0",
      "vue": "^2.6.11",
      "vue-apollo": "^3.0.5",
      "vue-router": "^3.2.0",
      "vuex": "^3.4.0"
    },
  • 在main.js 入口文件引用前端配置差多就这些了 简单配置想对比较简单

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    import { ApolloClient } from 'apollo-client'
    import { createHttpLink } from 'apollo-link-http'
    import { InMemoryCache } from 'apollo-cache-inmemory'
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    import VueApollo from 'vue-apollo'
    

const httpLink = createHttpLink({ uri: '/graphql', // 这里可以是相对路径 也可以是绝对路径 如果是相对路径的话本地需要配置一下代理环境 如果是绝对路径 后端接口需要开放跨域权限 }) // 缓存 const cache = new InMemoryCache() // 创建 apollo 客户端 const apolloClient = new ApolloClient({ link: httpLink, cache, }) // 这里可以将对象保存了可以在接下来被所有子组件使用的 const apolloProvider = new VueApollo({ defaultClient: apolloClient, }) Vue.config.productionTip = false Vue.use(ElementUI) Vue.use(VueApollo) new Vue({ router, store, apolloProvider, render: h => h(App) }).$mount('#app')


- 然后在组件中使用编写我们graphql 查询语言 分别对应增删改查

import gql from "graphql-tag"; // 查询列表带参数查询 const getList = gql query($page: Int, $pageSize: Int) { list(page: $page, pageSize: $pageSize) { rows { id content state } count } }; // 新增 const crateList = gql mutation ($content: String) { createList(content: $content) }; // 修改 const updataList = gql mutation($id: String, $content: String) { updataList(id: $id, content: $content) }; // 删除 const deleteList = gql mutation($id: [String]) { deleteList(id: $id) };

- 因为前面我们将apollo 这个挂在到vue 根节点下 这里我们直接可以通过 this.$apollo来进行调用 发起请求 这样前端基本查询请求已经写完了
- 关于apollo 使用文档地址 https://apollo.vuejs.org/zh-cn/guide/ g

// 列表 init() { this.$apollo .query({ query: getList, variables: { page: this.query.page, pageSize: this.query.pageSize, }, // 缓存数据 可以设置不同缓存策略 cache-and-network,no-cache,network-only fetchPolicy: "network-only", }) .then((res) => { this.tableData = res.data.list; }) .catch((ex) => { console.log(ex); }); }, open(id, content) { let that = this; this.$prompt("请输入内容", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", inputValue: content ? content : "", }) .then(({ value }) => { debugger if (id && content) { this.updataList(id, value); } else { that.createList(value); } }) .catch((ex) => { this.$message({ type: "info", message: "取消输入", }); }); }, createList(value) { this.$apollo .mutate({ mutation: crateList, variables: { content: value, }, }) .then((res) => { this.$message({ type: "success", message: "新增成功", }); this.init(); }); }, updataList(id, content) { this.$apollo .mutate({ mutation: updataList, variables: { id: id, content: content, }, }) .then((res) => { this.$message({ type: "success", message: "更新成功", }); this.init(); }); }, confirm(id) { this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }) .then((res) => { this.deleteList(id); }) .catch(() => { this.$message({ type: "info", message: "已取消删除", }); }); }, deleteList(id) { this.$apollo .mutate({ mutation: deleteList, variables: { id: [id], }, }) .then((res) => { this.$message({ type: "success", message: "删除成功", }); this.init(); }); }, },

---
#### 后端部分

- 这里我用expres 来快速搭建一个 node 服务 首先还是安装相关的包依赖

dependencies": { "apollo-server-express": "^2.19.2", "axios": "^0.21.1", "body-parser": "^1.19.0", "colors": "^1.4.0", "cors": "^2.8.5", "express": "^4.17.1", "graphql": "^15.4.0", "mysql2": "^2.2.5", "nodemon": "^2.0.7", "npm": "^6.14.11", "sequelize": "^6.3.5" }, "devDependencies": { "express-graphql": "^0.12.0" }


- 服务端入口文件 main.js  大概配置就这样为了方便整个demo 演示流程 我这样变也进行数据库操作修改 还有第三方数据请求对比

const express = require('express') const app = express() const { graphqlHTTP } = require('express-graphql') const bodyParser = require('body-parser') const { SERVER } = require('./conf/index') const Query = require('./schema/query.js'); const Mutation = require('./schema/mutation.js'); const { GraphQLSchema, } = require('graphql') // const cors = require('cors') const schema = new GraphQLSchema({ query: Query, mutation: Mutation }) // app.use(cors()) app.use('/graphql', graphqlHTTP(req => { return { schema: schema, graphiql: true // 配置为true时候 可以在浏览器中输入 http://localhost:10086/graphql 将打开 Graphiql } })) app.listen(SERVER.PORT, () => { console.log(app listening on port http://localhost:${SERVER.PORT}) })

### schema
- 通过引用graphql 这个包来graphql来配置schema了提供了对应的数据类型用来返回我们的对象模型返回到前端,数据类型不能用js语法中的类型来表示字段 必须使用通过引用graphql 数据类型来声明 这里schema 分为2个部分  一个是query 和 mutation来分别对象 数据的增删改查 当然还有一个 是subscribtion 还有一个订阅的模式 

##### Query 部分

const { GraphQLList, // 数组列表 GraphQLObjectType, // 对象 GraphQLString, // 字符串 GraphQLInt, // int类型 GraphQLFloat, // float类型 GraphQLEnumType, // 枚举类型 GraphQLNonNull, // 非空类型 GraphQLSchema, // schema(定义接口时使用) GraphQLBoolean, // 布尔值 GraphQLID // 唯一的字符串 } = require('graphql') const TodoListServer = require('../server/TodoListServer') const { offsetPage } = require('../utils/offsetPage') const axios = require('axios')

const tagsObject = new GraphQLObjectType({ name: 'taglist', fields: () => ({ tags_name: { type: GraphQLString }, user_id: { type:GraphQLInt }, tags_id: { type: GraphQLInt }, }) }) // 列表返回的数据结构 const list = new GraphQLObjectType({ name: 'listData', description: 'list', fields: () =>({ id: { type: GraphQLID, description: 'id' }, content: { type: GraphQLString, description: '内容' }, state: { type: GraphQLInt, description: '状态' }, subList: { type: new GraphQLList(tagsObject), description: '三方数据', resolve: async(root, args, context) => { // 请求第三方数据 let {data} = await axios.get('http://www.csde.club/api/articlesTags/list') return data.data.list } } }) }) const sourceData = new GraphQLObjectType({ name: 'todolistData', description: '列表信息', fields: () =>({ rows: { type: new GraphQLList(list) }, count: { type: GraphQLInt, description: '数据总数' }, }), }) // 查询数据列表 const Query = new GraphQLObjectType({ name: 'TodoListQuery', description: '返回列表数据', fields: () => ({ list: { type: sourceData, // 定义返回的数据类型 args: { page: { type: GraphQLInt, name: '当前页数' }, pageSize: { type: GraphQLInt, name: '每页多少条'} }, resolve: async(root, args, context) => { const { pageParams } = offsetPage(args) // 查询数据库列表数据 const data = await TodoListServer.list(pageParams) return data } }, }), }) module.exports = Query

##### Mutation 修改数据

const { GraphQLID, GraphQLList, // 数组列表 GraphQLObjectType, // 对象 GraphQLString, // 字符串 GraphQLInt, // int类型 GraphQLFloat, // float类型 GraphQLEnumType, // 枚举类型 GraphQLNonNull, // 非空类型 GraphQLSchema, // schema(定义接口时使用) GraphQLBoolean // 布尔值 } = require('graphql') const TodoListServer = require('../server/TodoListServer')

const list = new GraphQLObjectType({ name: 'list', description: 'list', fields: () =>({ id: { type: GraphQLInt, description: 'id' }, content: { type: GraphQLString, description: '内容' }, state: { type: GraphQLInt, description: '状态' } }) }) const Mutation = new GraphQLObjectType({ name: 'TodoListUpdata', description: '对数据进行增删改', fields: () => ({ deleteList: { type: GraphQLBoolean, description:'删除数据', args: { id: { name: '删除id', type: new GraphQLList(GraphQLString), } }, resolve: async (root, args, context) => { let res = await TodoListServer.delete(args.id) return res } }, updataList: { type: GraphQLBoolean, description:'更新数据', args: { id: { type: GraphQLString, }, content: { type: GraphQLString }, }, resolve: async (root, args, context) => { let res = await TodoListServer.update(args.id, args.content) return res[0] } }, createList: { type: GraphQLInt, description: "新增数据", args: { content: { type: GraphQLString }, }, resolve: async (root, args, context) => { let res = await TodoListServer.create(args.content) return res.id } } }), }) module.exports = Mutation

- 写好这些后 我们可以直接打开浏览器测试 我们的graphql 接口 http://localhost:10086/graphql

分页查询查询我们想要的数据返回需要的字段
> ![image](BFB4D465B3C945FC8E65315CB942C07C)

- 新增数据
> ![image](214AFF438A9245D9957E7A8F44AA9917)

- 删除数据
> ![image](C9A02728F756480D8FC6F7AB2EEAD18C)
- 更新数据
> ![image](063AF8A88CD146C7AD9AF59F359E4AE6)

- 这样基本算是完成列表的一些基本操作修改

## 回顾
- 在前面vue中有集成了 graphql提供配置套件 可以很快速方便无缝的衔接在我们的项目中去 在使用 Apollo 过程中 我发现 在发送graphql请求的时候 貌似全部都是post 请求 翻阅了下文档没有看到如果去设置请求的方式。 Apollo 发送请求方式 用是fetch api来做http请求  如果在使用 fetch 请求中 因为fetch请求 不能设置请求超时的问题 也许Apollo 可能做对这方面的处理。
- graphql 也可以用axios 来进行请求处理 也可以用postman 请求 如果单纯只是用fetch 来做请求 可能需要做很封装 服务端异常的处理  但是apollo已经将这些问题都解决了 而且还对数据做缓存的处理 而且提供些扩展的组件进行使用

- 通过将 GraphQL 作为技术栈的一部分,前端开发人员可以自由地查询所需的数据,而后端开发人员可以将客户端应用需求与后端系统架构分离。如果在使用 GraphQL 的过程中可能会在其现有的后端服务之上构建一个 GraphQL API 层来进行数据过滤
- 无论是 graphql 或者是REST  这2者并没谁取代谁的  我觉得他们是可以互相同时存在的并不影响 

### 最后项目的地址

评论 (0)

用户名
邮箱
评论

    Copyright © 2020 darkNightMan All Rights Reserved Pro 黔ICP备20005477号