網站首頁 編程語言 正文
什么是 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,雖然可以使用中間件的方式將原業務的請求進行代理,這無疑也將增加復雜度和資源的消耗
原文鏈接:https://juejin.cn/post/7152810866772017159
相關推薦
- 2022-12-29 React修改數組對象的注意事項及說明_React
- 2022-04-08 swift表格控件使用方法詳解(UITableview)_Swift
- 2022-09-15 python?Pandas庫read_excel()參數實例詳解_python
- 2022-12-13 深入了解Go的HttpClient超時機制_Golang
- 2022-08-29 python中requests庫安裝與使用詳解_python
- 2022-03-27 Python編程入門指南之函數_python
- 2023-10-15 MVCC和BufferPool緩存機制
- 2022-01-31 RuntimeError: scatter(): Expected dtype int64 for
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支