GraphQL 这门新技术在去年就开始火热起来,今年也在不少技术周刊、论坛上看到关于这门新技术的研究和讨论。所以做为一名前端开发,紧跟技术潮流是必须的 🤣,周末便花了点时间对 GraphQL 进行了相关学习,学习过程当中写了一些简单的 demo,在此过程当中发现这玩意是真的香啊,因此决定要开篇博客来记录下这个过程。html
当学习一本新技术时,首先就要去了解这门新技术的定位和一些相关概念,而后就要去研究它的诞生可以解决哪些痛点和带来哪些爽点。我我的习惯是直接去官方文档找到这些疑问的答案。打开官网,你就看到了下面有很醒目的一句话:前端
一种用于 API 的查询语言vue
还附加了几句颇有意思的话:java
说实话我看到这些第一反应就是感受很酷,但也给我带来了不少疑惑,单从纸面上看仍是比较难理解的,没有实际操做的话就很难体会上面说的那些特征。因此我开始了一波学习和 demo 的尝试 🤔node
GraphQL 一些关键概念包含 Type
,Schema
, Query
, Mutation
等,下面会分别作一下简单的说明,具体仍是要结合实际代码进行分析。react
顺着文档看,首先就看到到了关于查询的相关使用,所谓的查询就是向服务端获取你要想的数据,好比我要查全部的用户列表,webpack
// 先定义 User 数据结构
type User {
id: Int!
name: String!
age: Int!
}
// query 查询
query {
// 返回一个User类型的集合
userList() : [User!]
// 能够传参查询
// 根据id查询用户信息
orderUser(id: Int!) : User
}
复制代码
在 REST
风格接口应该是这样子的git
GET /api/v1/userList
GET /api/V1/userList/:id/
复制代码
这时候,你会对GraphQL
的解析---查询语言会有点小小的感触了github
还有,query
和type
的使用息息相关,因此在使用query
的时候,必定要先去了解type
类型系统,关于type
类型系统在官网的文档 上已经写得很是详细,这里我就不具体说了web
GraphQL 中的 Mutation
是用来改变服务器上的数据的。对应着 REST
风格中的 PUT
,DELETE
,POST
。Mutation
的语法风格和Query
很相似。关键在解析Mutation
过程当中会有所不一样。还有值得注意的是,查询字段时,是并行执行,而变动字段时,是线性执行,一个接着一个。
好比说,我要变动一个用户user
的名字
mutation {
// 经过参数 id 去查到对应的用户信息
MutationUserName(id: Int!, name: String!) : User!
}
复制代码
解析 MutationUserName
Mutation: {
MutationUserName(_, { id, name }) {
const user = userList.find(val => val.id === id);
if (!user) {
throw new Error(`找不到id为 ${id} 的user`);
}
user.name = name
return user;
}
}
复制代码
在 GraphQL 中,Schema
主要是用来描述数据的形态,哪些数据能被查询到,因此在 Schema
中主要定义可用数据的数据类型。这么说吧,你想要查到的数据都必需要在 Schema
中进行定义,因此这里是须要写不少 type
的,这里还须要统必定义 Query
和 Mutation
,也就是要把上面那些定位所有放到这里来
🌰:
type User {
id: Int!
name: String!
age: Int!
}
type Query {
userList() : [User]
orderUser(id: Int!) : User
}
mutation {
MutationUserName(id: Int!, name: String!) : User
}
复制代码
很很基础的内容大概就是酱紫,下面应该要开始来一波实战操做了
Apollo-GraphQL 是基于 GraphQL 封装的一种实现,它能够在服务上进行分层,包括
REST
api 和 数据库,它包含客户端和服务端,还有 GUI 开发工具,让开发人员能够快速上手进行开发。
我在 google 关于 GraphQL 的时候,就发现了这个平台,在官方文档上看到了一些讲解和入门练习。发现其入口门槛其实并不高,安装几个依赖即可快速启动服务端,客户端也支持了 vue
、react/rn
、ng
等热门的前端框架。我这里的 demo 也是根据文档教学一步步作出来的,下面就来说讲个人 demo 实现过程和一些思路。
具体想法
这边采用 apollo-server-express
快速搭建服务端
首先安装依赖
yarn add apollo-server-express express graphql
复制代码
好,这个 demo 只须要上面三个工具
对于apollo-server
,比较基本的就是要搞清楚 schema 和 resolvers 应该如何定义,这些理解起来仍是比较容易,但若是想玩得很溜,确定须要投入大量的时间去研究。
其实就是
const server = new ApolloServer({
typeDefs,
resolvers
});
复制代码
定义好 typeDefs(schema) 和 resolvers,即可快速启动
首先,就拿本人博客文章为例,把 mock 的数据源造好
[
{
"id": 1,
"title": "记一次系统前端底层升级总结",
"date": "2018-11-11",
"introduction": "最近参与了一个比较大的类后台管理系统的前端开发(vue技术栈),并负责了该系统的底层升级,升级过程期间,遇到了很多问题,在解决问题的过程当中学到了不少,趁着今天双11没啥事作,那么就花点时间总结下升级系统的过程吧",
"category": "vue",
"isRead": false
},
{
"id": 2,
"title": "Vue实践小结(长期更新)",
"date": "2018-11-04",
"introduction": "近期都在用 Vue 全家桶进行项目开发,过程当中不免会遇到很多问题,这篇博客主要就是记录开发过程当中遇到的问题,和每一个问题对应的解决方案。此外,Vue 框架和周边生态会一直更新,以及发布新功能,在实践过程当中总会遇到一些所谓的“坑”,我也会把填坑过程记录于此。坑是填不完的,这篇博客也是写不完的。",
"category": "vue",
"isRead": false
},
{
"id": 3,
"title": "如何用Koa2返回文本和图片流以及解决乱码事件",
"date": "2018-04-05",
"introduction": "前两天作项目的时候,碰到了一个要在客户端(浏览器)中实现预览 txt 文档和图片的小需求,在开发过程当中遇到了一些有趣的小插曲---客户端读取 txt 时出现各类奇怪乱码,图片就没问题。一时半会还没找到好的解决方法,由于那天又恰好周五,因此在周末的时候,我决定宅在家里好好研究如何解决这个有趣的现象。",
"category": "koa2",
"isRead": false
}
][
({
"id": 1,
"html": "<h1>记一次系统前端底层升级总结</h1>"
},
{
"id": 2,
"html": "<h1>Vue实践小结(长期更新)</h1>"
},
{
"id": 3,
"html": "<h1>如何用Koa2返回文本和图片流以及解决乱码事件</h1>"
})
]
复制代码
而后,要开始思考如何定义 Schema 中的 type
我这边一共定义了如下这些 type
type Article {
id: Int!
title: String!
date: String!
introduction: String!
category: String
isRead: Boolean!
}
type ArticleContent {
id: Int!
html: String!
}
type Category {
num: Int!,
name: String!
}
type Query {
fetchArticles: [Article]
getAllCategories: [Category]
getArticleContent(id: Int!): ArticleContent
}
type Mutation {
articleIsRead(id: Int!): Article
}
复制代码
把 这些 schema 转换为 typeDefs, 须要用到
const { gql } = require("apollo-server-express");
module.exports = gql`上面定义的type`;
复制代码
定义 resolvers
resolvers
实际上是 query
和 mutation
的实现过程。也就是说这里会进行数据库的查询或者是 api 的调用等等,最终放回的结果在这里出来。
我这边的实现大概是这样的
const articles = require("../data/articles.json");
const articleContent = require("../data/articleContent.json");
const resolvers = {
Query: {
fetchArticles() {
return articles;
},
getAllCategories() {
return articles.reduce((pre, cur) => {
const cate = pre.find(_ => _.name === cur.category);
if (cate) {
cate.num++;
} else {
const obj = {
name: cur.category,
num: 1
};
pre.push(obj);
}
return pre;
}, []);
},
getArticleContent(_, { id }) {
return articleContent.find(val => val.id === id);
}
},
Mutation: {
// 标记已读
articleIsRead(_, { id }) {
const article = articles.find(val => val.id === id);
if (!article) {
throw new Error(`找不到id为 ${id} 的文章`);
}
if (article.isRead) {
return article;
}
article.isRead = true;
return article;
}
}
};
module.exports = resolvers;
复制代码
好了,typeDefs 和 resolvers 搞定了,写点服务器启动脚本
const express = require("express");
const { ApolloServer } = require("apollo-server-express");
const typeDefs = require("./schema");
const resolvers = require("./resolvers");
const PORT = 4000;
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: `/graphql`,
settings: {
"editor.theme": "light"
}
}
});
server.applyMiddleware({ app });
app.listen(PORT, () =>
console.log(
`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`
)
);
复制代码
而后 node server.js
一下看看
启动不报错的话,在浏览器http://localhost:4000/graphql
能够看到图形化页面
试试 query 查询
服务端搭建完成 🎉
最近在用 vue 比较多,因此就直接用 vue create xxx
进行一顿操做来弄了,主要也是基于 vue-apollo 文档 进行学习和编写
安装?
yarn add vue-apollo graphql apollo-boost
import Vue from "vue";
import ApolloClient from "apollo-boost";
import VueApollo from "vue-apollo";
Vue.use(VueApollo);
const apolloClient = new ApolloClient({
// 你须要在这里使用绝对路径
uri: "http://localhost:4000/graphql"
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
new Vue({
el: "#app",
apolloProvider,
render: h => h(App)
});
复制代码
配置支持 .gql || .graphql
文件后缀的 webpack loader
vue.config.js
module.exports = {
// 支持 gql 文件
chainWebpack: config => {
config.module
.rule("graphql")
.test(/\.(graphql|gql)$/)
.use("graphql-tag/loader")
.loader("graphql-tag/loader")
.end();
}
};
复制代码
这样就能够导入xxx.gql
文件了,不必定非要在.vue
文件或者.js
文件里面写查询语句了
就是这么简单 😁
so,使用?
接下来就试一下查询多个数据,好比说一块儿查询博客文章列表和分类信息。验证一下是否是官方说的获取多个资源,只用一个请求那么神奇
import gql from "graphql-tag";
const fetchDataGql = gql` { fetchArticles { id title date introduction category isRead } getAllCategories { num name } } `;
export default {
data() {
return {
articles: [],
categories: []
};
},
apollo: {
fetchData() {
const vm = this;
return {
query: fetchDataGql,
update(data) {
vm.articles = data.fetchArticles;
vm.categories = data.getAllCategories;
}
};
}
}
};
复制代码
打开浏览器控制台看看
确实是只有一次请求就能获取到一个页面上全部的资源!最近在作项目的时候,遇到一个页面要请求 5 个REST
接口,有些接口常常返回了不少页面上用不到的,简单来讲就是多余的数据,这样不只浪费了服务器资源,先后端对接起来也不方便,因此 GraphQL 能够很好地解决这个痛点,真是香啊!
ui 界面上随便写点
好了,查询搞定了,下面试试 变动mutation
需求是点击某篇文章,让这篇文章的 title 有个已读标识(opacity: 0.4)
服务端那边已经定义好,客户端代码核心实现
const mArticleISRead = gql` mutation articleIsRead($id: Int!) { articleIsRead(id: $id) { id title date introduction category isRead } } `
methods: {
mutationIsRead(id) {
this.$apollo.mutate({
mutation: mArticleISRead,
variables: {
id
},
update: (store, { data: { articleIsRead } }) => {
console.log(articleIsRead);
}
});
}
},
复制代码
模板层增长一些判断
<router-link :class="{isRead: item.isRead}" @click.native="mutationIsRead(item.id)" :to="{ name: 'content', params: { id: item.id } }" >
{{ item.title }}
</router-link>
复制代码
测试
done!
写到这里,让我感受好奇的是,我从新跳转回 article 页面,并无从新触发 ajax 请求,后来看了文档才发现这是因为 Apollo-GraphQL 自带了运行时的 cache,变动数据的某个字段是不须要从新去获取最新的列表的,Apollo 会智能去识别而后自动触发视图的 render。这样的话,我在想,这样既然了 Apollo 的缓存机制,是否是就不须要像 vuex、redux、mobX 这些状态管理工具了呢?毕竟都是运行时的数据,若是 Apollo 的 cache 机制能解决状态管理层面上的问题,那么会减小不少项目层级的复杂度吧。凭空想是想不出的,只有在实际项目中才能找到真正的答案。
好了,GraphQL 的服务端和客户端均已搭建,一些自身的想法也获得了相关印证,不断探索、不断验证想法正是学习的一种乐趣 😁
上面代码均已上传到 github
GraphQL 牛刀小试结束。关于 GraphQL 之后能不能取代 RESTapi 仍是一个很富有争议的话题。REST 已经发展多年,它解决了之前的很多问题但也留下了很多问题,GraphQL 仍是比较新的,相信还有不少人都不知道有这个技术,即便它已诞生了好多年...仍是那句话,每一个新的技术的诞生,必然是为了解决某些问题,以及提升生产效率,这样才有它们存在的价值和意义。