GraphQL——由Facebook创建的接口规范,用于API的查询,为前后端数据交互提供了新的查询方式。
一、RESTFul的痛点
1、面对复杂场景的API粒度设计问题
在多端应用开发需求时,如Web、App、小程序... Web页面所需的描述字段往往更多一点,为满足web页面的接口需求,对于App/小程序而言,接口字段是冗余的,这就造成了流量浪费、产生性能问题~ 同理,为了提高加载速度,通过合并减少请求次数的方式,也会生成同样问题。
- 粗粒度设计
流量性能浪费:造成移动端不必要的流量损耗; - 细粒度设计
大量接口产生:不同端、不同页面、不同接口、同一页面多次请求造成函数爆炸;
2、API版本规划问题
接口字段等可能变动,之类的版本迭代问题
3、双向通讯的需求
由于http只能由浏览器向服务器单向推送消息,如股票等信息实时推送、支付等功能无法实现,虽然可以通过websocket实现,但不同的通信协议,接口规范无法统一表现风格
4、对于组件需要各自管理状态的难点
目前程序通常都是通过使用统一状态管理工具,对于实现组件各自的状态管理这种新的编码风格相对比较麻烦。
二、三个核心功能及工作方式
- Queries :提供查询类接口
- Mutations: 处理状态的变化
- Subscriptions: 订阅后端状态变化、通知前端
1、Queries功能很类似解构赋值
query {
hello,
hi: hello, // 前端可以通过别名使用
books(id:'1'), // 只从books中查找到id为1的数据
books(id:'1'){ // 只从books中查id为1的date、author
date,
author
}
}
任意字段、对应别名均可自由定制,从而能避免接口版本更新问题,前端不会受后端接口版本变化而影响。
2、Mutations主要处理增删改逻辑
mutation {
createBook( name:'创建新书', author:'作者' ) {
id // id等其他信息
}
}
3、Subscriptions前端订阅后端发送的消息
subscription{
subsBooks
}
前端可以自由定制接口,后端可以按需返回,基于websocket实现的。
三、开发后端程序主要用Apollo Server框架
Apollo Server可以单独作为服务器,也可以作为Express、Koa的插件被使用。实例基础:
const { ApolloServer,gql } = require('apollo-server');
// Schema_gql接口定义
const typeDefs = gql`
type Query {
hello: String
}
`
// 解释器的实现
const resolvers = {
Query: {
hello: ()=>'Hello World'
}
}
// 创建服务器实例
const server = new ApolloServer({typeDefs,resolvers});
// 启动
server.listen().then( ({url}) => { console.log('运行会重新启动playground') } );
详例开发图书的查询接口:
const { ApolloServer,gql } = require('apollo-server');
// Schema_gql接口定义
const typeDefs = gql`
type Query {
book: [Book], // Book为新的自定义类型
books( id:String ):Book
}
type Book {
id: String,
name: Sting,
author: String
}
type Mutation {
createBook( name:String, author:String ): Books!, // 必须返回
clearBook: Boolean // 是否清空成功
}
`
// 创建数据_Book列表,是一个匿名函数
const books = ( ()=>{
Array(6).fill()
.map( (v,i)=>({
id: 'book'+i,
name: 'Name'+i,
author: 'Author'+i
}) )
} )();
// 解释器的实现
const resolvers = {
Query: {
// hello: ()=>'Hello World'
books: ()=> books,
book: ( parent,{id} )=>{
return books.find( v=> v.id===i );
}
},
Mutation: {
createBook: ( parent,args ) => {
const book = { ...args,id:books.length+1+''};
books.push(book);
return book;
},
clearBook: () => {
books.length = 0;
return true;
}
}
}
// 创建服务器实例
const server = new ApolloServer({typeDefs,resolvers});
// 启动
server.listen().then( ({url}) => { console.log('运行会重新启动playground') } );
Subscription的用法:
const { PubSub, withFilter} = require('apollo-server');
// PubSub为订阅发布模式
const typeDefs = gql`
type Subscription{
subsBook: Boolean
}
`
// 创建订阅发布实例
const pubsub = new PubSub();
// 解释器中添加
const resolvers = {
Subscripion: {
subsBooks:{
subscribe: withFilter(
( parent, variables ) => pubsub.asyncIterator('UPDATE_BOOK'),
() => true // 过滤UPDATE_BOOK消息、发布true
)
}
},
// 发布消息订阅在Mutation中
Mutation: {
createBook: ( parent,args ) => {
const book = { ...args,id:books.length+1+''};
books.push(book);
pubsub.publish('UPDATE_BOOK',{
subsBooks: true
})
return book;
},
}
}
四、前端调用订阅(示React用法)
import React from 'react';
import { useMutation } from '@/apollo/react-hooks'; // 借用useMutation钩子实现
import { gql } from 'apollo-boost';
// 第一个查询
const CREATE_BOOK = gql`
mutation CreateBook( $name: String!, $author: String! ){
createBook( name: $name, author: $author ){
id,
name,
author
}
}
`
// 第二个查询
const CLEAR_BOOK = gql`
mutation {
clearBook
}
`
// 创基函数式组件
function Mutation {
// 导出create、clear方法,执行时会调用mutation向后端创建数据
const [ create, {data} ] = useMutation(CREATE_BOOK);
const [ clear ] = useMutation(CLEAR_BOOK);
// 页面且放两个按钮
return (
{/* 每次点击都会创建新数据 */}
)
}
export default Mutation;
五、使用
引入Mutation控件、
加载即可
用声明式数据管理取代统一管理的方式,可以使用useSubscription钩子实现数据订阅,为前后端交互提供新的可能,在每个组件内部订阅数据的状态,写法更简单明确、用法更容易~