什么是 GraphQL
GraphQL由Facebook发起 ,其手机客户端自2012年起 ,就全面采用了GraphQL查询语言 , 2015年 , Facebook全面开源了第一份GraphQL规范 。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述 ,是一种规范 ,使得客户端能够准确地获得它需要的数据 ,而且没有任何冗余.
GraphQL出现的意义
传统API存在的主要问题:
接口数量众多维护成本高:接口的数量通常由业务场景的数量决定 ,为了尽量减少接口数量 ,服务端工程师通常会对业务做抽象,首先构建粒度较小的数据接口 ,再根据业务场景对数据接口进行组合 ,对外暴露业务接口,即便这样 ,服务端对前端暴露的接口数量还是非常多 ,因为业务总是多变的 。
接口扩展成本高:出于带宽的考虑移动端我们要求接口返回尽量少的字段,PC 端通常要展现更多字段;考虑首屏性能 ,我们又要求对接口做合并;传统 API 应对这些需求 ,前后端都面临改造 ,成本较高。
接口响应的数据格式无法预知:由于接口文档几乎总是不能及时更新 ,前端工程师无法预知接口响应的数据格式 ,影响前端开发进度 。
GraphQL 如何解决问题
请求参数在发送到服务端之前会先经过 GraphQL Client 转换成客户端 Schema ,这段 Schema 其实是一段 query 开头的字符串 ,描述了客户端的对数据的述求:调用哪个方法 ,传递什么样的参数 ,返回哪些字段 。服务端拿到这段 Schema 之后,通过事先定义好的服务端 Schema 接收请求参数并执行对应的 resolve 函数提供数据服务 。
GraphQL基本语法
参考 [GraphQL][1] 官网文档
标量类型
GraphQL 自带一组默认标量类型: Int:有符号 32 位整数 。 Float:有符号双精度浮点值 。 String:UTF‐8 字符序列 。 Boolean:true 或者 false 。 ID:ID 标量类型表示一个唯一标识符 ,通常用以重新获取对象或者作为缓存中的键 。ID 类型使用和 String 一样的方式序列化 。
对象类型
一个 GraphQL schema 中的最基本的组件是对象类型 ,它就表示你可以从服务上获取到什么类型的对象,以及这个对象有什么字段
type Character {
name: String!
list: [Episode!]!
}
myField: [String!]
myField: null
myField: []
myField: [a, b]
myField: [a, null, b]
myField: [String]!
myField: null
myField: []
myField: [a, b]
myField: [a, null, b]
GraphQL 对象类型上的每一个字段都可能有零个或者多个参数 ,
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
枚举类型
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
这表示无论我们在 schema 的哪处使用了 Episode ,都可以肯定它返回的是 NEWHOPE 、EMPIRE 和 JEDI 之一。
对象类型 、标量以及枚举是 GraphQL 中你唯一可以定义的类型种类 。但是当你在 schema 的其他部分使用这些类型时,或者在你的查询变量声明处使用时 ,你可以给它们应用额外的类型修饰符来影响这些值的验证 。
type Character {
name: String!
list: [Episode]!
}
GraphQL 内置指令
GraphQL 中内置了两款逻辑指令 ,指令跟在字段名后使用。
@include 当条件成立时 ,查询此字段
query {
search {
actors @include(if: $queryActor) {
name
}
}
}
@skip 当条件成立时 ,不查询此字段
query {
search {
comments @skip(if: $noComments) {
from
}
}
}
操作类型:指定本请求体要对数据做什么操作 ,类似与 REST 中的 GET POST 。GraphQL 中基本操作类型有 query 表示查询 ,mutation 表示对数据进行操作 ,例如增删改操作 ,subscription 订阅操作 。
操作名称:操作名称是个可选的参数 ,操作名称对整个请求并不产生影响,只是赋予请求体一个名字 ,可以作为调试的依据。
变量定义:在 GraphQL 中 ,声明一个变量使用符号开头,冒号后面紧跟着变量的传入类型 。如果要使用变量 ,直接引用即可 ,例如上面的movie就可以改写成movie(name:符号开头,冒号后面紧跟着变量的传入类型 。如果要使用变量 ,直接引用即可 ,例如上面的 movie 就可以改写成 movie(name: 符号开头 ,冒号后面紧跟着变量的传入类型 。如果要使用变量 ,直接引用即可 ,例如上面的movie就可以改写成movie(name:name) 。
query Hero($episode: Int!, $withFriends: Boolean!) {
hero(episode: $episode) {
name
friends @include(if: $withFriends) {
name
}
}
}
什么是 Apollo
Meteor 团队有着很丰富的数据流控制经验 ,他们发现了 Relay 的不便之处 ,引领业界通过使用他们开发的 Apollo 享受到更简洁的接口 ,Apollo 是基于GraphQL的全栈解决方案集合 。包括了 apollo-client 和 apollo-server ;从后端到前端提供了对应的 lib ,使开发使用 GraphQL 更加的方便 。
apollo-server
apollo-server是一个在nodejs上构建grqphql服务端的web中间件 。支持express,koa 等框架 。 参考 [apollo-server][2] 官网文档
处理流程
主要是通过官方graphql-js库进行处理
1.解析阶段
为了识别客户端 Schema , graphql-js 定义了一系列的特征标识符:
export const TokenKind = Object.freeze({
BANG: !,
DOLLAR: $,
PAREN_L: (,
PAREN_R: ),
SPREAD: ...,
COLON: :,
EQUALS: =,
BRACKET_L: [,
BRACKET_R: ],
...
});
并定义了 AST 语法树规范 ,规定语法树支持以下节点:
export const Kind = Object.freeze({
// Name
NAME: Name,
// Document
DOCUMENT: Document,
OPERATION_DEFINITION: OperationDefinition,
VARIABLE_DEFINITION: VariableDefinition,
VARIABLE: Variable,
// Values
INT: IntValue,
FLOAT: FloatValue,
STRING: StringValue,
BOOLEAN: BooleanValue,
...
});
有了特征字符串与 AST 语法树规范,GraphQL Server 对客户端 Schema 进行逐字符扫描,如果客户端 Schema 不符合服务端定义的 AST 规范 ,解析过程会直接抛出语法异常 。
2.校验阶段
校验阶段用于验证客户端 Schema 是否按照服务端 Schema 定义好的方式获取数据 ,比如:获取数据的方法名是否有误,必填项是否有值等等 ,校验范围一共有几十种 ,不一一举例。
{
"errors":[
{
"message":"Cannot query field "getU" on type "Query". Did you mean "getUser"?",
"locations":[
{
"line":3,
"column":9
}
]
}
]
}
不仅返回结构化的报错信息 ,还非常人性化的告诉你正确的调用方式是什么 。校验阶段通过之后会进入执行阶段.
3.执行阶段
执行阶段依赖的输入为:解析阶段的产出物 document ,服务端 Schema;其中 document 准确描述了客户端对数据的述求:请求哪个方法 ,参数是什么 ,需要哪些字段;服务端 Schema 描述了提供数据的方式 。执行服务端 Schema 中的 resolve 函数 ,得到执行阶段的输出。每个类型的每个字段都由一个 resolver 函数支持 ,该函数由 GraphQL 服务器开发人员提供 。
Schema
Schema可以说是GraphQL最具核心的部分 ,其描述了整个接口向外暴露的形式;像Restful API,我们会定义一个查询所有人的接口url定义为:/api/v1/user/getUsers ,而查询人具体信息的接口url为:/api/v1/user/getUserById,前端人员调用起来很直观 。但是graphql是完全不一样的使用方式 ,其向前端暴露的url就一个像/api/graphql之类的,那这么多接口怎么区分呢?
一个graphql接口都有一个Schema定义 ,其定义三种操作方式:query(查询),mutation(变更)和subscription(监听)。再往下延伸 ,一个查询中包含多个field,也就是多种不同的查询 ,比如query user查询人 ,query message查询消息 ,query weather查询天气 ,通过这些就实现了Restful API使用多个url来达到不同操作的效果 。
给server端带来的便利性
由于 GraphQL 通过客户端 Schema 而不是通过 URL 描述数据述求 ,所以理论上服务端只需要对客户端暴露一个地址即可 , 解决了接口数量众多维护成本高的问题; 同时 ,服务端提供的是全量字段 ,客户端可按需获取 ,面对接口扩展的需求,服务端没有开发成本;
import express from express;
import { graphiqlExpress, graphqlExpress } from apollo-server-express;
const app = express();
app.use(/graphql, graphqlExpress({
schema,
}));
app.use(/graphiql, graphiqlExpress({
endpointURL: /graphql
}));
apollo-client
参考 [apollo-client][3] 官网文档
创建client
import ApolloClient from "apollo-boost";
const client = new ApolloClient({
uri: "https://48p1r2roz4.sse.codesandbox.io"
});
在我们将Apollo Client连接到React之前 ,让我们先尝试发送查询 。记住首先导入gql用于将查询字符串解析为查询文档 。
import gql from "graphql-tag";
...
client.query({
query: gql`
{
rates(currency: "USD") {
currency
}
}
`
})
.then(result => console.log(result));
将client注入到react
react-apollo提供ApolloProvider组件 ,ApolloProvider类似于redux的provider 。它会把apollo客户端放入到React app的上下文里,以便在组件树的任何地方都是可以获取到apollo客户端 。
import React from "react";
import { render } from "react-dom";
import { ApolloProvider } from "react-apollo";
const App = () => (
<ApolloProvider client={client}>
<div>
<h3>My first Apollo app ?</h3>
</div>
</ApolloProvider>
);
render(<App />, document.getElementById("root"));
数据请求
1.通过react-apollo提供的graphql函数获取数据 ,并connect到组建中 ,就可以在组件的this.props中看到多了个data对象,
import React, { Component } from react;
import { graphql } from react-apollo;
import gql from graphql-tag;
export const USERS_QUERY = gql`
query UserQuery($pageNum: Int,$pageSize:Int){
users(pageNum:$pageNum,pageSize:$pageSize ) {
pageNum
pageSize
total
data {
userName
}
}
}
`;
const withQuery = graphql(USERS_QUERY, {
options: () => ({
variables: {
pageNum: 3,
pageSize: 8,
},
}),
});
class List extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { data: { loading, error, users } } = this.props;
if (loading) {
return <div className="loading">Loading...</div>;
}
if (error) return `Error! ${error.message}`;
const { total } = users;
};
return (
<div>
<p className="total">总共<span>{total}</span>人</p>
</div>
);
}
}
export default withQuery(List);
data中包含loading, error, users等字段
当React安装Query组件时 ,Apollo Client会自动触发查询 。如果想延迟触发查询 ,直到用户执行操作(例如单击按钮) ,该怎么办?对于这种情况 ,可以使用ApolloConsumer组件并直接调用client.query() 。
import React, { Component } from react;
import { ApolloConsumer } from react-apollo;
class DelayedQuery extends Component {
state = { dog: null };
onDogFetched = dog => this.setState(() => ({ dog }));
render() {
return (
<ApolloConsumer>
{client => (
<div>
{this.state.dog && <img src={this.state.dog.displayImage} />}
<button
onClick={async () => {
const { data } = await client.query({
query: GET_DOG_PHOTO,
variables: { breed: "bulldog" }
});
this.onDogFetched(data.dog);
}}
>
Click me!
</button>
</div>
)}
</ApolloConsumer>
);
}
}
2.通过apollo提供的组件获取
import gql from "graphql-tag";
import { Query } from "react-apollo";
const GET_DOG_PHOTO = gql`
query Dog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
const DogPhoto = ({ breed }) => (
<Query query={GET_DOG_PHOTO} variables={{ breed }} pollInterval={500}>
{({ loading, error, data, startPolling, stopPolling }) => {
if (loading) return null;
if (error) return `Error!: ${error}`;
return (
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
);
}}
</Query>
);
通过设置pollInterval为500 ,每隔0.5秒看到一个新的小狗图像 。 当我们从Query组件中获取数据时 ,看看Apollo Client幕后发生的事情 。
1.当Query组件安装时 ,Apollo Client会为我们的查询创建一个observable。我们的组件通过Apollo Client缓存订阅查询结果 。
2.首先 ,我们尝试从Apollo缓存加载查询结果 。如果它不在那里 ,我们将请求发送到服务器。
3.数据恢复后,我们将其标准化并将其存储在Apollo缓存中 。由于Query组件订阅了结果 ,因此它会自动更新数据 。
数据缓存
创建本地缓存
import { ApolloClient } from apollo-client
import { withClientState } from apollo-link-state
import { HttpLink } from apollo-link-http
import { InMemoryCache } from apollo-cache-inmemory
import { resolvers, typeDefs, defaults } from ../client/index
const cache = new InMemoryCache()
const client = new ApolloClient({
cache, // 本地数据存储
link: withClientState({ resolvers, defaults, cache, typeDefs }).concat(
new HttpLink({
uri: http://localhost:4001/graphql,
opts: {
credentials: cross-origin,
},
})
),
})
要直接与缓存交互 ,可以使用Apollo Client方法readQuery,readFragment ,writeQuery和writeFragment。
总结
如果使用 GraphQL ,那么后端将不再产出 API,而是将 Controller 层维护为 Resolver ,和前端约定一套 Schema ,这个 Schema 将用来生成接口文档 ,前端直接通过 Schema 或生成的接口文档来进行自己期望的请求 。
GraphQL 的优缺点
优点
所见即所得:所写请求体即为最终数据结构 减少网络请求:复杂数据的获取也可以一次请求完成 Schema 即文档:定义的 Schema 也规定了请求的规则 类型检查:严格的类型检查能够消除一定的认为失误
缺点
增加了服务端实现的复杂度:一些业务可能无法迁移使用 GraphQL ,虽然可以使用中间件的方式将原业务的请求进行代理 ,这无疑也将增加复杂度和资源的消耗
以上就是GraphQL在react中的应用示例详解的详细内容 ,更多关于GraphQL react应用的资料请关注本站其它相关文章!
声明:本站所有文章 ,如无特殊说明或标注 ,均为本站原创发布 。任何个人或组织 ,在未征得本站同意时,禁止复制、盗用 、采集 、发布本站内容到任何网站 、书籍等各类媒体平台 。如若本站内容侵犯了原著者的合法权益 ,可联系我们进行处理 。