在只學習graphql client端知識的過程當中,咱們經常須要一個graphql ide來提示graphql語法,以及實現graphql的server端來進行練手。
graphql社區提供了graphiql讓咱們使用html
graphiql (npm):一個交互式的運行於瀏覽器中的 GraphQL IDE.
但graphiql提供的live demo基本打不開,難道剛接觸graphql就要本身實現graphql的server端?
好在github用graphql寫了一套api,咱們能夠去這裏,登錄後便可體驗一把graphql。前端
關於graphql的基礎知識能夠去這裏看看vue
graphql在前端實現有如下方案。node
Relay (github) (npm):Facebook 的框架,用於構建與 GraphQL 後端交流的 React 應用。
Apollo Client (github):一個強大的 JavaScript GraphQL 客戶端,設計用於與 React、React Native、Angular 2 或者原生 JavaScript 一同工做。
graphql-request:一個簡單的彈性的 JavaScript GraphQL 客戶端,能夠運行於全部的 JavaScript 環境(瀏覽器,Node.js 和 React Native)—— 基本上是 fetch 的輕度封裝。
Lokka:一個簡單的 JavaScript GraphQL 客戶端,能夠運行於全部的 JavaScript 環境 —— 瀏覽器,Node.js 和 React Native。
nanogql:一個使用模板字符串的小型 GraphQL 客戶端庫。
從npm download數量上看Apollo Client是最多的,而且Apollo也有服務端的解決方案,因此這裏選擇Apollo Client做爲graphql的client端
apollo client對於web 框架都有具體的實現,可是我更但願能像axios那樣去使用graphql,而不是每套web框架都要去學一下具體實現,那樣會折騰死本身。
webpack
// 使用vue-cli初始化項目 vue init webpack-simple my-project npm i
npm i apollo-cache-inmemory apollo-client apollo-link apollo-link-http npm i graphql graphql-tag
. ├── index.html ├── package.json ├── package-lock.json ├── README.md ├── src │ ├── App.vue │ ├── graphql // 接口 │ │ ├── search.graphql │ │ └── index.js // export全部接口 │ ├── main.js │ └── utils │ └── graphql.js // 對Apollo-client封裝 └── webpack.config.js
接下來對apollo-client進行封裝,加上中間件(實現相似於axios攔截器的效果)。
graphql.jsios
import ApolloClient from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import { HttpLink } from 'apollo-link-http' import { onError } from 'apollo-link-error' import { ApolloLink, from } from 'apollo-link' const token = '598ffa46592d1c7f57ccf8173e47290c6db0d549' const Middleware = new ApolloLink((operation, forward) => { // request時對請求進行處理 console.log('Middleware', operation, forward) }) const Afterware = new ApolloLink((operation, forward) => { return forward(operation).map(response => { // 服務器返回數據 console.log('Afterware--response', response) return response }) }) const errorLink = onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) graphQLErrors.map(({ message, locations, path }) => console.log( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, ), ); if (networkError) console.log(`[Network error]: ${networkError}`); }); const httpLink = new HttpLink({ uri: 'https://api.github.com/graphql', // 配置請求url headers: { // 配置header Authorization: `Bearer ${token}` } }) const cache = new InMemoryCache() // 緩存 export default new ApolloClient({ link: from([Middleware, Afterware, errorLink, httpLink]), cache })
配置webpack支持.graphql文件git
// 在rules下添加如下規則 { test: /\.(graphql|gql)$/, exclude: /node_modules/, loader: 'graphql-tag/loader', }
search.graphqlgithub
query searchR ($keyword: String!) { search (query: $keyword , type: REPOSITORY){ userCount } }
在/graphql/index.js export全部接口web
import client from '../utils/graphql' // import gql from 'graphql-tag' import * as searchGql from './search.graphql' /** searchGql模塊 **/ export const search = (params) => client.query({query: searchGql.search, variables: params})
到這裏咱們已經能夠直接調用/graphql/下導出的functionsql
具體實現就不貼出來了,所有代碼已經放到github,歡迎star。
run的時候有記得把token換成本身的,由於個人token有可能已經失效。
graphql實現分頁有如下兩種方式:
對於遊標分頁Relay(Facebook家的Graphql庫) 定了一套規範 Relay-style cursor pagination
基於偏移量的分頁實現簡單,但存在如下問題:
性能問題,雖然可使用 「延遲關聯」 解決,但會使sql語句變得複雜
# 假設 有一個 product商品表,當商品表數量足夠多時,這個查詢會變得很是緩慢, SELECT id, name FROM product LIMIT 1000, 20; # 若是咱們提供一個邊界值,好比id,不管翻頁到多麼後面,其性能都會很好 SELECT id, name FROM product WHERE id > 1000 LIMIT 20;
刪除列表數據時,致使獲取下一頁的數據缺失
# 假設 總共有11條數據,一頁顯示10條,總頁數爲 2 頁。 # 當調用接口刪除 第 1 頁的 1 條數據,而後進行翻頁時,由於只剩下10條數據,因此下面的sql會查不到數據。 SELECT id, name FROM product LIMIT 10, 10;
基於遊標/ID 的分頁,也存在硬傷:
因此咱們須要同時支持這兩種分頁。
Relay 定義了 PageInfo
,Edges
,Edge Types
,Node
,Cursor
等對象 用於實現靈活的分頁。👇是Relay給出的一個query例子。
{ user { id name friends(first: 10, after: "opaqueCursor") { edges { cursor node { id name } } pageInfo { hasNextPage } } } }
friends 鏈接會返回一個對象,這個對象的名稱會以 Connection
結尾,如friendConnection
, Connection
中必需包含PageInfo
,Edges
。
Relay 在返回的遊標鏈接上提供了一個 PageInfo 對象,其必需包含 hasPreviousPage, hasNextPage。
遊標是不透明的,而且它們的格式不該該被依賴,建議用 base64 編碼它們。
Edges
:類型爲 LIST
,必需包含Edge Types
Edge Types
:類型爲 Object
,必需包含 Node
,Cursor
Node
: 類型能夠爲 標量,枚舉,對象,接口,聯合類型,此字段沒法返回列表。Cursor
: 類型爲String
經過Edges,列表數據中每一項都包含一個Cursor、Node,但咱們基本不多須要Cursor。
下一頁分頁,須要兩個參數。
服務器最多返回 first 條 遊標id以後(不包括遊標id) 的數據。
上一頁分頁,須要兩個參數。
服務器最多返回 last 條 遊標id以前(不包括遊標id) 的數據。
first跟last不該該同時使用,這會使判斷 上一頁/下一頁 變得麻煩。
咱們須要在query時,把跳過多少條記錄 這個參數給到 service 端,後端根據這個值 是否 存在 去使用不一樣分頁方式。Prisma把這個參數命名爲 skip
,這裏咱們與其保持一致。
{ user { id name friends(first: 10, after: "opaqueCursor", skip: 1) { edges { cursor node { id name } } pageInfo { hasNextPage } } } }
👉分頁例子:包含先後端
後端項目目錄結構如👇
./app/ ├── extend ├── graphql │ ├── common # 定義公用的 Schema 和類型,如pageInfo │ │ ├── resolver.js │ │ ├── scalars │ │ │ └── cursor.js # 定義cursor數據類型 │ │ └── schema.graphql │ ├── mutation │ │ └── schema.graphql │ ├── query │ │ └── schema.graphql │ └── user # user │ ├── connector.js │ ├── resolver.js │ └── schema.graphql
定義 PageInfo
對象,Cursor
標量 。
# graphql/common/schema.graphql scalar Cursor type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: Cursor endCursor: Cursor }
PageInfo
的resolver
層
// graphql/common/resolver 'use strict'; module.exports = { Cursor: require('./scalars/cursor'), // eslint-disable-line PageInfo: { hasNextPage(root) { // 在Connector層(如UserConnector)返回PageInfo對象時,咱們能夠返回 function 或 boolean,function可以支持更加複雜的判斷 if (typeof root.hasNextPage === 'function') return root.hasNextPage(); return root.hasNextPage; }, hasPreviousPage(root) { if (typeof root.hasPreviousPage === 'function') return root.hasPreviousPage(); return root.hasPreviousPage; }, }, };