GraphQL+Koa2實(shí)現(xiàn)服務(wù)端API結(jié)合Apollo+Vue

一招盲、GraphQL介紹

1.1 簡(jiǎn)介

GraphQL 是一種新的 API 的查詢語(yǔ)言鳍刷,它提供了一種更高效甸各、強(qiáng)大和靈活 API 查詢。它 是由 Facebook 開發(fā)和開源谋竖,目前由來(lái)自世界各地的大公司和個(gè)人維護(hù)兄渺。GraphQL 對(duì)你的 API 中的數(shù)據(jù)提供了一套易于理解的完整描述择浊,使得客戶端能夠準(zhǔn)確地獲得它需要的數(shù)據(jù),而且 沒有任何冗余刹碾。它彌補(bǔ)了 RESTful API(字段冗余,擴(kuò)展性差座柱、無(wú)法聚合 api迷帜、無(wú)法定義數(shù)據(jù) 類型物舒、網(wǎng)絡(luò)請(qǐng)求次數(shù)多)等不足

注意:GraphQL 是 api 的查詢語(yǔ)言,而不是數(shù)據(jù)庫(kù)戏锹。從這個(gè)意義上說冠胯,它是數(shù)據(jù)庫(kù)無(wú)關(guān)的, 而且可以在使用 API 的任何環(huán)境中有效使用景用,我們可以理解為 GraphQL 是基于 API 之上的一 層封裝涵叮,目的是為了更好,更靈活的適用于業(yè)務(wù)的需求變化

GraphQL 可以用在常見各種服務(wù)器端語(yǔ)言以及客戶端語(yǔ)言中

  • 服務(wù)器端語(yǔ)言:C# / .NET伞插、Clojure割粮、Elixir、Erlang媚污、Go舀瓢、Groovy、Java耗美、JavaScript京髓、PHP、Python商架、 Scala堰怨、Ruby

  • 客戶端語(yǔ)言:js、React + React Native蛇摸、Angular备图、Vue.js、Apollo Link赶袄、Native iOS揽涮、Native Android、 Scala.js

  • 中文文檔:http://graphql.cn

  • Github: https://github.com/facebook/graphql

GraphQL 出現(xiàn)的歷史背景

當(dāng)提起API設(shè)計(jì)的時(shí)候饿肺,大家通常會(huì)想到SOAP(一種簡(jiǎn)單的基于 XML 的協(xié)議)蒋困,RESTful 等設(shè)計(jì)方式,從 2000 年 RESTful 的理論被提出的時(shí)候敬辣,在業(yè)界引起了很大反響雪标,因?yàn)檫@種 設(shè)計(jì)理念更易于用戶的使用,所以便很快的被大家所接受溉跃。

我們知道 REST 是一種從服務(wù) 器公開數(shù)據(jù)的流行方式汰聋。當(dāng) REST 的概念被提及出來(lái)時(shí),客戶端應(yīng)用程序?qū)?shù)據(jù)的需求相 對(duì)簡(jiǎn)單喊积,而開發(fā)的速度并沒有達(dá)到今天的水平烹困。

因此 REST 對(duì)于許多應(yīng)用程序來(lái)說是非常 適合的。然而在業(yè)務(wù)越發(fā)復(fù)雜乾吻,客戶對(duì)系統(tǒng)的擴(kuò)展性有了更高的要求時(shí)髓梅,API 環(huán)境發(fā)生了巨 大的變拟蜻,RESTful 顯得心有余而力不足。比如:字段冗余枯饿,擴(kuò)展性差酝锅、無(wú)法聚合 api、無(wú)法 定義數(shù)據(jù)類型奢方、網(wǎng)絡(luò)請(qǐng)求次數(shù)多

GraphQL 的出現(xiàn)整好彌補(bǔ)了 RESTful APi 的不足

使用 GraphQL 的公司

目前已經(jīng)有很多的公司在使用 GraphQL(https://graphql.org/users/

1.2 為什么推薦 GraphQL 而不是 RESTful API

在過去的十多年中搔扁,REST 已經(jīng)成為設(shè)計(jì) web api 的標(biāo)準(zhǔn)(雖然只是一個(gè)模糊的標(biāo)準(zhǔn))。

它提供了一些很棒的想法蟋字,比如無(wú)狀態(tài)服務(wù)器和結(jié)構(gòu)化的資源訪問稿蹲。

然而 REST api 表 現(xiàn)得過于僵化,無(wú)法跟上訪問它們的客戶的快速變化的需求

RESTful API 不足

  • 擴(kuò)展性(多個(gè)終端需要返回不同的字段)鹊奖,單個(gè) RESTful 接口返回?cái)?shù)據(jù)越來(lái)越 臃腫苛聘。前端對(duì)于真正用到的字段是沒有直觀映像的,僅僅通過 url 地址忠聚,無(wú)法預(yù)測(cè)也無(wú) 法回憶返回的字段數(shù)目和字段是否有效设哗,接口返回 50 個(gè)字段,但卻只用 5 個(gè)字段两蟀,造 成字段冗余网梢,擴(kuò)展性差,單個(gè) RESTful 接口返回?cái)?shù)據(jù)越來(lái)越臃腫
  • API 聚合問題赂毯,某個(gè)前端展現(xiàn)战虏,實(shí)際需要調(diào)用多個(gè)獨(dú)立的 RESTful API 才能獲 取到足夠的數(shù)據(jù),導(dǎo)致網(wǎng)絡(luò)請(qǐng)求次數(shù)多
  • 前后端字段頻繁改動(dòng)欢瞪,導(dǎo)致類型不一致,錯(cuò)誤的數(shù)據(jù)類型可能會(huì)導(dǎo)致網(wǎng)站出錯(cuò) 尤其是在業(yè)務(wù)多變的場(chǎng)景中徐裸,很難在保證工程質(zhì)量的同時(shí)快速滿足業(yè)務(wù)需求

GraphQL 的優(yōu)點(diǎn)

  • 吸收了 RESTful API 的特性
  • 所見即所得 各種不同的前端框架和平臺(tái)可以指定自己需要的字段遣鼓。查詢的返回結(jié)果就是輸 入的查詢結(jié)構(gòu)的精確映射

客戶端可以自定義 Api 聚合

如果設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)是從屬的,直接就能在查詢語(yǔ)句中指定;即使數(shù)據(jù)結(jié)構(gòu)是獨(dú) 立的重贺,也可以在查詢語(yǔ)句中指定上下文骑祟,只需要一次網(wǎng)絡(luò)請(qǐng)求,就能獲得資源和子 資源的數(shù)據(jù)气笙。

代碼即是文檔

GraphQL 會(huì)把 schema 定義和相關(guān)的注釋生成可視化的文檔次企,從而使得代碼的變更,直接就反映到最新的文檔上潜圃,避免 RESTful 中手工維護(hù)可能會(huì)造成代碼缸棵、 文檔不一致的問題

參數(shù)類型強(qiáng)校驗(yàn)

  • RESTful 方案本身沒有對(duì)參數(shù)的類型做規(guī)定,往往都需要自行實(shí)現(xiàn)參數(shù)的校驗(yàn)機(jī)制谭期, 以確保安全堵第。
  • 但 GraphQL 提供了強(qiáng)類型的 schema 機(jī)制吧凉,從而天然確保了參數(shù)類型的合法性

二、GraphQl類型系統(tǒng)

2.1 GraphQl類型

可以將GraphQL的類型系統(tǒng)分為標(biāo)量類型(ScalarTypes踏志,標(biāo)量類型)和其他高級(jí)數(shù)據(jù)類型阀捅,標(biāo)量類型即可以表示最細(xì)粒度數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)類型,可以和JavaScript的原始類型對(duì)應(yīng)

GraphQL規(guī)范目前規(guī)定支持的標(biāo)量類型有

  • Int:有符號(hào)32位整數(shù) -- GraphQLInt
  • Float:有符號(hào)雙精度浮點(diǎn)值 -- GraphQLFloat
  • StringUTF‐8字符序列 -- GraphQLString
  • Booleantrue或者false -- GraphQLBoolean
  • ID(GraphQLID):ID標(biāo)量類型表示一個(gè)唯一標(biāo)識(shí)符针余,通常用以重新獲取對(duì)象或者作為緩存中的鍵饲鄙。ID類型使用和String一樣的方式序列化;然而將其定義為ID意味著并不需要可讀型圆雁。

GraphQL其他高級(jí)數(shù)據(jù)類型包括

  • Object:對(duì)象(newGraphQLObjectType)

用于描述層級(jí)或者樹形數(shù)據(jù)結(jié)構(gòu)忍级。對(duì)于樹形數(shù)據(jù)結(jié)構(gòu)來(lái)說,葉子字段的類型都是標(biāo)量數(shù)據(jù)類型摸柄。幾乎所有GraphQL類型都是對(duì)象類型颤练。Object類型有一個(gè)name字段,以及一個(gè)很重要的fields字段驱负。fields字段可以描述出一個(gè)完整的數(shù)據(jù)結(jié)構(gòu)嗦玖。例如一個(gè)表示地址數(shù)據(jù)結(jié)構(gòu)的GraphQL對(duì)象為

const AddressType=newGraphQLObjectType({
    name:'Address',
    fields:{
        street:{
            type:GraphQLString
        },
        number:{
            type:GraphQLInt
        },
        formatted:{
            type:GraphQLString,
            resolve(obj){
                return obj.number+''+obj.street
            }   
        }
    }
});
  • Interface:接口用于描述多個(gè)類型的通用字
  • Union:聯(lián)合類型用于描述某個(gè)字段能夠支持的所有返回類型以及具體請(qǐng)求真正的返回類型
  • Enum:枚舉用于表示可枚舉數(shù)據(jù)結(jié)構(gòu)的類型
  • InputObject:輸入對(duì)象
  • List:列表

列表是其他類型的封裝,通常用于對(duì)象字段的描述跃脊。例如下面PersonType類型數(shù)據(jù)的parents和children字段

const PersonType=newGraphQLObjectType({
    name:'Person',
    fields:()=>({
        parents:{type:newGraphQLList(Person)},
        children:{type:newGraphQLList(Person)},
    })
})
  • Non-Null:不能為Null

Non-Null強(qiáng)制類型的值不能為null宇挫,并且在請(qǐng)求出錯(cuò)時(shí)一定會(huì)報(bào)錯(cuò)±沂酰可以用于必須保證值不能為null的字段器瘪。例如數(shù)據(jù)庫(kù)的行的id字段不能為null

const RowType=newGraphQLObjectType({
    name:'Row',
    fields:()=>({
        id:{
            type:newGraphQLNonNull(GraphQLString)
        }
    })
})

2.2 GraphQl查詢語(yǔ)言

GraphQL規(guī)范支持兩種操作

  • query:僅獲取數(shù)據(jù)(fetch)的只讀請(qǐng)求
  • mutation:獲取數(shù)據(jù)后還有寫操作的請(qǐng)求

新版本的GraphQL還支持subscription,這是為了處理訂閱更新這種比較復(fù)雜的實(shí)時(shí)數(shù)據(jù)更新場(chǎng)景而設(shè)計(jì)的操作

三绘雁、Express中集成GraphQl 實(shí)現(xiàn) Server API

3.1 安裝mongodb造數(shù)據(jù)

使用mongodb做數(shù)據(jù)庫(kù)演示橡疼,mac安裝mongodb亮蒋,brew install mongodb-community

# 進(jìn)入mongo shell
mongo 

# 創(chuàng)建數(shù)據(jù)庫(kù)
use graphql (graphql數(shù)據(jù)庫(kù)不存在會(huì)自動(dòng)創(chuàng)建)

# 創(chuàng)建nav郊楣、articlecate集合插入數(shù)據(jù)
db.nav.insert({name: "標(biāo)題1", url: "/", sort: 1, add_time: "2022-06-30"})
db.nav.insert({name: "標(biāo)題2", url: "/", sort: 1, add_time: "2022-06-30"})
db.nav.insert({name: "標(biāo)題3", url: "/", sort: 1, add_time: "2022-06-30"})

db.articlecate.insert({name: "分類1", description: "描述", keywords: "關(guān)鍵詞", status: 1})
db.articlecate.insert({name: "分類2", description: "描述", keywords: "關(guān)鍵詞", status: 1})
db.articlecate.insert({name: "分類3", description: "描述", keywords: "關(guān)鍵詞", status: 1})

或者導(dǎo)入數(shù)據(jù)庫(kù)數(shù)據(jù)

下載數(shù)據(jù)庫(kù)文件解壓并導(dǎo)入mongodb即可 https://blog.poetries.top/db/koa.zip

導(dǎo)入mongodb數(shù)據(jù)庫(kù)

mongorestore -h localhost:27017 -d koa-demo(數(shù)據(jù)庫(kù)名稱挺物,不存在會(huì)自動(dòng)創(chuàng)建) ./dump(本地?cái)?shù)據(jù)文件路徑)

3.2 express集成GraphQl

https://github.com/graphql/express-graphql

npm install express-graphql graphql--save

引入express-graphql配置中間件

app完善配置

// app.js
var express=require('express');
var DB=require('./model/db.js'); 
const graphqlHTTP = require('express-graphql');
const GraphQLDefaultSchema = require('./schema/default.js');

var app=express(); 

// 配置中間件
app.use('/graphql', graphqlHTTP({
    schema: GraphQLDefaultSchema,
    graphiql: true // 線上環(huán)境關(guān)閉漾月,開發(fā)環(huán)境開啟
}));

//配置路由
app.get('/',function(req,res){
    res.send('hello express');
})

app.listen(3000,()=>console.log("http://localhost:3000"));

定義GraphQLSchema模型

  • 新建schema/default.js
  • 定義Schema
const DB=require('../model/db.js'); /*引入DB庫(kù)*/

const {
    GraphQLObjectType,
    GraphQLString,
    GraphQLInt,
    GraphQLSchema,
    GraphQLList
} =require('graphql')

//1关斜、獲取導(dǎo)航列表     定義導(dǎo)航的schema類型
var NavSchema=new GraphQLObjectType({
    name:'nav',
    fields:{
        _id:{
            type:GraphQLString
        },
        title:{
            type:GraphQLString
        },
        url:{
            type:GraphQLString
        },
        sort:{
            type:GraphQLInt
        },
        status:{
            type:GraphQLInt
        },
        add_time:{
            type:GraphQLString
        }
    }
})

var ArticleCateSchema=new GraphQLObjectType({
    name:'articlecate',
    fields:{
        _id:{
            type:GraphQLString
        },
        title:{
            type:GraphQLString
        },
        description:{
            type:GraphQLString
        },
        keywords:{
            type:GraphQLString
        } , 
        status:{
            type:GraphQLInt
        } 
    }
})

//2波材、定義一個(gè)跟       根里面定義調(diào)用導(dǎo)航Schema類型的方法
var RootSchema=new GraphQLObjectType({
    name:'root',
    fields:{
        oneNavList:{  //方法名稱:定義調(diào)用導(dǎo)航Schema類型的方法
            type:NavSchema,  //方法的類型, 方法返回的參數(shù)必須和NavSchema里面定義的類型一致
            args:{id:{type:GraphQLString}}, //參數(shù)
            async resolve(parent,args){  //執(zhí)行的操作

                // args.id 獲取調(diào)用方法傳入的值
                var id=args.id;

                var navList=await DB.find('nav',{"_id":DB.getObjectId(id)});
                return navList[0];               
            }
        },
        navList:{
            type:GraphQLList(NavSchema),  
            async resolve(parent,args){               
                var navList=await DB.find('nav',{});
                return navList;               
            }
        },
        articleCateList:{
            type:GraphQLList(ArticleCateSchema),
            async resolve(parent,args){               
                var articlecateList=await DB.find('articlecate',{});
                return articlecateList; 
            }
        },
        oneArticleCateList:{
            type:ArticleCateSchema, 
            args:{id:{type:GraphQLString}},
            async resolve(parent,args){             
                var id=args.id;
                var articlecateList=await DB.find('articlecate',{"_id":DB.getObjectId(id)});
                return articlecateList[0];   //要返回一個(gè)json對(duì)象
            }
        }
    }

})

//3沽甥、把根掛載到 GraphQLSchema
module.exports=new GraphQLSchema({
    query:RootSchema
})

編寫數(shù)據(jù)庫(kù)操作方法

/**

 * http://mongodb.github.io/node-mongodb-native

 * http://mongodb.github.io/node-mongodb-native/3.0/api/
 */

//DB庫(kù)
var MongoDB=require('mongodb');
var MongoClient =MongoDB.MongoClient;
const ObjectID = MongoDB.ObjectID;

var Config= {
    dbUrl: 'mongodb://localhost:27017/',
    dbName: 'graphql' // 數(shù)據(jù)庫(kù)名
}

class Db {
    static getInstance(){   /*1屯换、單例  多次實(shí)例化實(shí)例不共享的問題*/
        if(!Db.instance){
            Db.instance=new Db();
        }
        return  Db.instance;
    }

    constructor(){
        this.dbClient=''; /*屬性 放db對(duì)象*/
        this.connect();   /*實(shí)例化的時(shí)候就連接數(shù)據(jù)庫(kù)*/

    }
    connect(){  /*連接數(shù)據(jù)庫(kù)*/
      let _that=this;
      return new Promise((resolve,reject)=>{
          if(!_that.dbClient){         /*1杠娱、解決數(shù)據(jù)庫(kù)多次連接的問題*/
              MongoClient.connect(Config.dbUrl,{ useNewUrlParser: true },(err,client)=>{
                  if(err){
                    reject(err)
                  }else{
                    _that.dbClient=client.db(Config.dbName);
                    resolve(_that.dbClient)
                  }
              })
          }else{
            resolve(_that.dbClient);
          }
      })
    }
    /*
     DB.find('user',{})  返回所有數(shù)據(jù)

     DB.find('user',{},{"title":1})    返回所有數(shù)據(jù)  只返回一列

     DB.find('user',{},{"title":1},{   返回第二頁(yè)的數(shù)據(jù)
        page:2,
        pageSize:20,
        sort:{"add_time":-1}
     })
     js中實(shí)參和形參可以不一樣      arguments 對(duì)象接收實(shí)參傳過來(lái)的數(shù)據(jù)
    * */
    find(collectionName,json1,json2,json3){
        if(arguments.length==2){
            var attr={};
            var slipNum=0;
            var pageSize=0;
        }else if(arguments.length==3){
            var attr=json2;
            var slipNum=0;
            var pageSize=0;
        }else if(arguments.length==4){
            var attr=json2;
            var page=parseInt(json3.page) ||1;
            var pageSize=parseInt(json3.pageSize)||20;
            var slipNum=(page-1)*pageSize;
            if(json3.sort){
                var sortJson=json3.sort;
            }else{
                var sortJson={}
            }
        }else{
            console.log('傳入?yún)?shù)錯(cuò)誤')
        }
       return new Promise((resolve,reject)=>{
            this.connect().then((db)=>{
                //var result=db.collection(collectionName).find(json);
                var result =db.collection(collectionName).find(json1,{fields:attr}).skip(slipNum).limit(pageSize).sort(sortJson);
                result.toArray(function(err,docs){
                    if(err){
                        reject(err);
                        return;
                    }
                    resolve(docs);
                })

            })
        })
    }
    update(collectionName,json1,json2){
        return new Promise((resolve,reject)=>{
            this.connect().then((db)=>{
                //db.user.update({},{$set:{}})
                db.collection(collectionName).updateOne(json1,{
                    $set:json2
                },(err,result)=>{
                    if(err){
                        reject(err);
                    }else{
                        resolve(result);
                    }
                })
            })
        })
    }
    insert(collectionName,json){
        return new  Promise((resolve,reject)=>{
            this.connect().then((db)=>{
                db.collection(collectionName).insertOne(json,function(err,result){
                    if(err){
                        reject(err);
                    }else{
                        resolve(result);
                    }
                })
            })
        })
    }
    remove(collectionName,json){
        return new  Promise((resolve,reject)=>{
            this.connect().then((db)=>{
                db.collection(collectionName).removeOne(json,function(err,result){
                    if(err){
                        reject(err);
                    }else{
                        resolve(result);
                    }
                })
            })
        })
    }
    getObjectId(id){  /*mongodb里面查詢 _id 把字符串轉(zhuǎn)換成對(duì)象*/
        return new ObjectID(id);
    }
    //統(tǒng)計(jì)數(shù)量的方法
    count(collectionName,json){
        return new  Promise((resolve,reject)=> {
            this.connect().then((db)=> {
                var result = db.collection(collectionName).count(json);
                result.then(function (count) {
                        resolve(count);
                    }
                )
            })
        })
    }
}

module.exports=Db.getInstance();

打開本地調(diào)試

http://localhost:3000/graphql

四挽牢、Koa中集成GraphQl實(shí)現(xiàn) Server API

下載數(shù)據(jù)庫(kù)文件解壓并導(dǎo)入mongodb即可 https://blog.poetries.top/db/koa.zip

  • 導(dǎo)入mongodb數(shù)據(jù)庫(kù) mongorestore -h localhost:27017 -d koa-demo(數(shù)據(jù)庫(kù)名稱,不存在會(huì)自動(dòng)創(chuàng)建) ./dump(本地?cái)?shù)據(jù)文件路徑)
  • 導(dǎo)出mongodb數(shù)據(jù)庫(kù) mongodump -h localhost:27017 -d test(數(shù)據(jù)庫(kù)名稱) -o ./dump

文檔地址 https://github.com/chentsulin/koa-graphql

npm install graphql koa-graphql koa-mount --save

實(shí)現(xiàn)導(dǎo)航列表API摊求、文章分類API禽拔、文章列表API、文章詳情API 、文章列表分頁(yè)查詢API奏赘、以及文章列表關(guān)聯(lián)文章分類實(shí)現(xiàn)聚合API

4.1 app完善配置

// app.js 

var Koa=require('koa');

var router = require('koa-router')(); 

const mount = require('koa-mount');

const graphqlHTTP = require('koa-graphql');

var GraphQLDefaultSchema=require('./schema/default.js')


const DB=require('./model/db.js');

var app=new Koa();


//配置中間件
app.use(mount('/graphql', graphqlHTTP({
    schema: GraphQLDefaultSchema,
    graphiql: true
})));


router.get('/',async (ctx)=>{
    ctx.body="首頁(yè)";
})

router.get('/getNavList',async (ctx)=>{
    
    var navList=await DB.find('nav',{});     
     ctx.body=navList;
})  

app.use(router.routes());   /*啟動(dòng)路由*/
app.use(router.allowedMethods());
app.listen(4000, ()=>console.log('http://localhost:4000'));

4.2 定義schema模型

// schema/default.js
const DB=require('../model/db.js');

//文章分類api接口     //文章列表api接口 (分頁(yè))     //文章詳情api接口(api聚合 獲取分類信息)

const {
    GraphQLObjectType,
    GraphQLString,
    GraphQLInt,
    GraphQLFloat,
    GraphQLList,
    GraphQLSchema,
    GraphQLID
}=require('graphql')

//1寥闪、定義導(dǎo)航的schema
var NavSchema=new GraphQLObjectType({
    name:'nav',
    fields:{
        _id:{
            type:GraphQLString
        },
        title:{
            type:GraphQLString
        },url:{

            type:GraphQLString
        },
        sort:{
            type:GraphQLInt

        },
        status:{
            type:GraphQLString
        },
        add_time:{
            type:GraphQLString
        }
    }
})


//定義文章分類的schema
var ArticleCateSchema=new GraphQLObjectType({
    name:'articlecate',
    fields:{
        _id:{type:GraphQLString},
        title:{type:GraphQLString},
        description:{ type: GraphQLString },
        keywords:{ type: GraphQLInt },
        pid:{type:GraphQLInt},
        add_time:{ type: GraphQLString },
        status:{ type: GraphQLInt }      
    }
})



//定義文章的schema
var ArticleSchema=new GraphQLObjectType({
    name:'article',
    fields:{
        _id:{type:GraphQLID},
        pid:{type:GraphQLID},    
        title:{ type: GraphQLString },
        author:{ type: GraphQLString },
        status:{type:GraphQLInt},
        is_best:{ type: GraphQLInt },
        is_hot:{ type: GraphQLInt },
        is_new:{ type: GraphQLInt },
        keywords:{ type: GraphQLString },
        description:{ type: GraphQLString },
        content:{ type: GraphQLString },
        sort:{ type: GraphQLInt },
        // 聚合查詢文章分類信息
        cateInfo:{
            type:ArticleCateSchema,
            async resolve(parent,args){
                // parent.pid 當(dāng)前新聞的分類id
                console.log(parent);

                var cateResult=await DB.find('articlecate',{"_id":DB.getObjectId(parent.pid)});

                return cateResult[0];

            }

        }
    }
})

//訂單商品的Schema  (order_item)
var OrderItem=new GraphQLObjectType({
    name:'orderitem',
    fields:{
        uid:{ type: GraphQLID },
        order_id:  { type: GraphQLID },
        product_title: { type: GraphQLString },
        product_id: { type: GraphQLID },    
        product_img: { type: GraphQLString },    
        product_price: { type: GraphQLFloat },  
        product_num: { type: GraphQLInt },        
        add_time: {
          type: GraphQLString        
        }      
    }
})

//訂單的Schema
var OrderSchema=new GraphQLObjectType({
    name:'order',
    fields:{
        _id:{type:GraphQLID},
        uid: { type:GraphQLID},
        all_price: { type: GraphQLInt },
        order_id: { type: GraphQLInt },
        name: { type: GraphQLString },  
        phone: { type: GraphQLString },    
        address:  { type: GraphQLString },    
        zipcode:  { type: GraphQLString },    
        pay_status:{ type: GraphQLInt},   // 支付狀態(tài): 0 表示未支付     1 已經(jīng)支付
        pay_type:{type: GraphQLString},      // 支付類型: alipay    wechat  
        order_status: {               // 訂單狀態(tài): 0 已下單  1 已付款  2 已配貨  3、發(fā)貨   4磨淌、交易成功   5疲憋、退貨     6、取消      
          type: GraphQLInt      
        },
        add_time: {
          type: GraphQLString          
        },
        // 聚合查詢訂單關(guān)聯(lián)的商品列表信息
        orderItems:{
            type:GraphQLList(OrderItem),
            async resolve(parent,args){
                //獲取當(dāng)前訂單對(duì)應(yīng)的商品 parent._id就是objectId
                var orderItemList=await DB.find('order_item',{"order_id":parent._id});
                return orderItemList;
            }

        }
    }
})



//2梁只、定義一個(gè)根 配置調(diào)用Schema的方法
var RootSchema=new GraphQLObjectType({
    name:'root',
    fields:{
        navList:{
            type:GraphQLList(NavSchema),
            async resolve(parent,args){
                var navList=await DB.find('nav',{});     
                return navList;
            }            
        },
        oneNavList:{
            type:NavSchema,
            args:{
                _id:{
                    type:GraphQLString
                },
                status:{
                    type:GraphQLString
                }
            },
            async resolve(parent,args){

                var oneNavList=await DB.find('nav',{"_id":DB.getObjectId(args._id),"status":args.status});     
                return oneNavList[0];

            }
        },
        articleCateList:{
            type:GraphQLList(ArticleCateSchema),
            async resolve(parent,args){

                var articlecateList=await DB.find('articlecate',{});     
                return articlecateList;
            }
        },
        articleList:{
            type:GraphQLList(ArticleSchema),
            args:{
                page:{
                    type:GraphQLInt
                },
                pageSize:{
                    type:GraphQLInt
                }
            },
            // 分頁(yè)查詢文章列表
            async resolve(parent,args){
                var page=args.page||1;
                var pageSize=args.pageSize||5;
                console.log(page,pageSize);
                var articleList=await DB.find('article',{},{},{
                    page,
                    pageSize:pageSize,
                    sort:{"add_time":-1}
                 });     

                return articleList;
            }
        },
        // 訂單列表
        orderList:{
            type:GraphQLList(OrderSchema),
            args:{
                page:{
                    type:GraphQLInt
                }
            },
            async resolve(parent,args){
                var page=args.page || 1;
                var orderList=await DB.find('order',{},{},{
                    page,
                    pageSize:3                    
                 });     
                return orderList;
            }
        },
        // 單個(gè)訂單信息
        oneOrderList:{
            type:OrderSchema,
            args:{
                _id:{
                    type:GraphQLID
                }
            },
            async resolve(parent,args){               
                var orderList=await DB.find('order',{"_id":DB.getObjectId(args._id)});     
                return orderList[0];
            }
        }
    }
})

//3缚柳、把查詢的根 掛載到GraphQLSchema
module.exports=new GraphQLSchema({
    query:RootSchema
})

4.3 編寫數(shù)據(jù)庫(kù)操作方法

// model/db.js
/**

 * http://mongodb.github.io/node-mongodb-native

 * http://mongodb.github.io/node-mongodb-native/3.0/api/
 */

//DB庫(kù)
var MongoDB=require('mongodb');
var MongoClient =MongoDB.MongoClient;
const ObjectID = MongoDB.ObjectID;

var Config= {
    url: 'mongodb://localhost:27017',
    dbName: 'koa-demo'
}

class Db{
    static getInstance(){   /*1、單例  多次實(shí)例化實(shí)例不共享的問題*/
        if(!Db.instance){
            Db.instance=new Db();
        }
        return  Db.instance;
    }

    constructor(){
        this.dbClient=''; /*屬性 放db對(duì)象*/
        this.connect();   /*實(shí)例化的時(shí)候就連接數(shù)據(jù)庫(kù)*/
    }

    connect(){  /*連接數(shù)據(jù)庫(kù)*/
      let _that=this;
      return new Promise((resolve,reject)=>{
          if(!_that.dbClient){         /*1搪锣、解決數(shù)據(jù)庫(kù)多次連接的問題*/
              MongoClient.connect(Config.dbUrl,{ useNewUrlParser: true },(err,client)=>{

                  if(err){
                      reject(err)
                  }else{
                      _that.dbClient=client.db(Config.dbName);
                      resolve(_that.dbClient)
                  }
              })
          }else{
            resolve(_that.dbClient);
          }
      })

    }
    /*

     DB.find('user',{})  返回所有數(shù)據(jù)


     DB.find('user',{},{"title":1})    返回所有數(shù)據(jù)  只返回一列


     DB.find('user',{},{"title":1},{   返回第二頁(yè)的數(shù)據(jù)
        page:2,
        pageSize:20,
        sort:{"add_time":-1}
     })
     js中實(shí)參和形參可以不一樣      arguments 對(duì)象接收實(shí)參傳過來(lái)的數(shù)據(jù)
    * */

    find(collectionName,json1,json2,json3){
        if(arguments.length==2){
            var attr={};
            var slipNum=0;
            var pageSize=0;
        }else if(arguments.length==3){
            var attr=json2;
            var slipNum=0;
            var pageSize=0;
        }else if(arguments.length==4){
            var attr=json2;
            var page=parseInt(json3.page) ||1;
            var pageSize=parseInt(json3.pageSize)||20;
            var slipNum=(page-1)*pageSize;

            if(json3.sort){
                var sortJson=json3.sort;
            }else{
                var sortJson={}
            }
        }else{
            console.log('傳入?yún)?shù)錯(cuò)誤')
        }
       return new Promise((resolve,reject)=>{
            this.connect().then((db)=>{
                //var result=db.collection(collectionName).find(json);
                var result =db.collection(collectionName).find(json1,{fields:attr}).skip(slipNum).limit(pageSize).sort(sortJson);
                result.toArray(function(err,docs){
                    if(err){
                        reject(err);
                        return;
                    }
                    resolve(docs);
                })

            })
        })
    }
    update(collectionName,json1,json2){
        return new Promise((resolve,reject)=>{
                this.connect().then((db)=>{

                    //db.user.update({},{$set:{}})
                    db.collection(collectionName).updateOne(json1,{
                        $set:json2
                    },(err,result)=>{
                        if(err){
                            reject(err);
                        }else{
                            resolve(result);
                        }
                    })

                })

        })

    }
    insert(collectionName,json){
        return new  Promise((resolve,reject)=>{
            this.connect().then((db)=>{

                db.collection(collectionName).insertOne(json,function(err,result){
                    if(err){
                        reject(err);
                    }else{

                        resolve(result);
                    }
                })


            })
        })
    }

    remove(collectionName,json){

        return new  Promise((resolve,reject)=>{
            this.connect().then((db)=>{

                db.collection(collectionName).removeOne(json,function(err,result){
                    if(err){
                        reject(err);
                    }else{

                        resolve(result);
                    }
                })


            })
        })
    }
    getObjectId(id){    /*mongodb里面查詢 _id 把字符串轉(zhuǎn)換成對(duì)象*/

        return new ObjectID(id);
    }
    //統(tǒng)計(jì)數(shù)量的方法
    count(collectionName,json){

        return new  Promise((resolve,reject)=> {
            this.connect().then((db)=> {

                var result = db.collection(collectionName).count(json);
                result.then(function (count) {

                        resolve(count);
                    }
                )
            })
        })

    }
}


module.exports=Db.getInstance();

啟動(dòng)服務(wù)

4.4 聚合查詢

聚合查詢文章分類信息秋忙,分類信息的方式要放在article的schema里面,這樣才能聚合查詢到

聚合查詢結(jié)果

查詢訂單构舟,聚合查詢訂單關(guān)聯(lián)的商品信息返回灰追,實(shí)現(xiàn)類似以下效果

// schema/default.js 
//訂單商品的Schema  (order_item)
var OrderItem=new GraphQLObjectType({
    name:'orderitem',
    fields:{
        uid:{ type: GraphQLID },
        order_id:  { type: GraphQLID },
        product_title: { type: GraphQLString },
        product_id: { type: GraphQLID },    
        product_img: { type: GraphQLString },    
        product_price: { type: GraphQLFloat },  
        product_num: { type: GraphQLInt },        
        add_time: {
          type: GraphQLString        
        }      
    }
})

//訂單的Schema
var OrderSchema=new GraphQLObjectType({
    name:'order',
    fields:{
        _id:{type:GraphQLID},
        uid: { type:GraphQLID},
        all_price: { type: GraphQLInt },
        order_id: { type: GraphQLInt },
        name: { type: GraphQLString },  
        phone: { type: GraphQLString },    
        address:  { type: GraphQLString },    
        zipcode:  { type: GraphQLString },    
        pay_status:{ type: GraphQLInt},   // 支付狀態(tài): 0 表示未支付     1 已經(jīng)支付
        pay_type:{type: GraphQLString},      // 支付類型: alipay    wechat  
        order_status: {               // 訂單狀態(tài): 0 已下單  1 已付款  2 已配貨  3、發(fā)貨   4狗超、交易成功   5弹澎、退貨     6、取消      
          type: GraphQLInt      
        },
        add_time: {
          type: GraphQLString          
        },
        // 聚合查詢訂單關(guān)聯(lián)的商品列表信息
        orderItems:{
            type:GraphQLList(OrderItem),
            async resolve(parent,args){
                //獲取當(dāng)前訂單對(duì)應(yīng)的商品 parent._id就是objectId
                var orderItemList=await DB.find('order_item',{"order_id":parent._id});
                return orderItemList;
            }

        }
    }
})


// 定義一個(gè)根 配置調(diào)用Schema的方法
var RootSchema=new GraphQLObjectType({
    name:'root',
    fields:{
        orderList:{
            type:GraphQLList(OrderSchema),
            args:{
                page:{
                    type:GraphQLInt
                }
            },
            async resolve(parent,args){
                var page=args.page || 1;
                var orderList=await DB.find('order',{},{},{
                    page,
                    pageSize:3                    
                 });     
                return orderList;
            }
        },
        oneOrderList:{
            type:OrderSchema,
            args:{
                _id:{
                    type:GraphQLID
                }
            },
            async resolve(parent,args){               
                var orderList=await DB.find('order',{"_id":DB.getObjectId(args._id)});     
                return orderList[0];
            }
        }
    }
})

查詢訂單詳情

需要哪些字段努咐,就返回哪些字段苦蒿,編輯器會(huì)自定提示

4.5 分頁(yè)查詢

//定義文章分類的schema
var ArticleCateSchema=new GraphQLObjectType({
    name:'articlecate',
    fields:{
        _id:{type:GraphQLString},
        title:{type:GraphQLString},
        description:{ type: GraphQLString },
        keywords:{ type: GraphQLInt },
        pid:{type:GraphQLInt},
        add_time:{ type: GraphQLString },
        status:{ type: GraphQLInt }      
    }
})



//定義文章的schema
var ArticleSchema=new GraphQLObjectType({
    name:'article',
    fields:{
        _id:{type:GraphQLID},
        pid:{type:GraphQLID},    
        title:{ type: GraphQLString },
        author:{ type: GraphQLString },
        status:{type:GraphQLInt},
        is_best:{ type: GraphQLInt },
        is_hot:{ type: GraphQLInt },
        is_new:{ type: GraphQLInt },
        keywords:{ type: GraphQLString },
        description:{ type: GraphQLString },
        content:{ type: GraphQLString },
        sort:{ type: GraphQLInt },
        // 聚合查詢文章分類信息
        cateInfo:{
            type:ArticleCateSchema,
            async resolve(parent,args){
                // parent.pid 當(dāng)前新聞的分類id
                console.log(parent);

                var cateResult=await DB.find('articlecate',{"_id":DB.getObjectId(parent.pid)});

                return cateResult[0];

            }

        }
    }
})


//2、定義一個(gè)根 配置調(diào)用Schema的方法
var RootSchema=new GraphQLObjectType({
    name:'root',
    fields:{
        articleCateList:{
            type:GraphQLList(ArticleCateSchema),
            async resolve(parent,args){

                var articlecateList=await DB.find('articlecate',{});     
                return articlecateList;
            }
        },
        articleList:{
            type:GraphQLList(ArticleSchema),
            args:{
                page:{
                    type:GraphQLInt
                },
                pageSize:{
                    type:GraphQLInt
                }
            },
            // 分頁(yè)查詢文章列表
            async resolve(parent,args){
                var page=args.page||1;
                var pageSize=args.pageSize||5;
                console.log(page,pageSize);
                var articleList=await DB.find('article',{},{},{
                    page,
                    pageSize:pageSize,
                    sort:{"add_time":-1}
                 });     

                return articleList;
            }
        },
    }
})

4.6 實(shí)現(xiàn)數(shù)據(jù)增加渗稍、修改佩迟、刪除

// scehma/default.js 
//增加 修改 刪除
// 定義根MutationRoot實(shí)現(xiàn)增刪改
var MutationSchema=new GraphQLObjectType({
    name:"mutation",
    fields:{
        addNav:{
            type:NavSchema,
            args:{
                title: {type: new GraphQLNonNull(GraphQLString)},     //表示title 和 url是必傳字段
                url: {type: GraphQLNonNull(GraphQLString)},
                sort: {type: GraphQLInt},
                status: {type: GraphQLString},
                add_time: {type: GraphQLString}
            },
            async resolve(parent, args) {
                var result = await DB.insert('nav', {title:args.title,
                    url:args.url,
                    sort:args.sort,
                    status:args.status,
                    add_time:new Date().getTime()
                });

                console.log(result.ops[0]);

                return result.ops[0];
            }
        },
        editNav:{
            type:NavSchema,
            args:{
                _id:{type: new GraphQLNonNull(GraphQLString)},
                title: {type: new GraphQLNonNull(GraphQLString)},     //表示title 和 url是必傳字段
                url: {type: GraphQLNonNull(GraphQLString)},
                sort: {type: GraphQLInt},
                status: {type: GraphQLString},
                add_time: {type: GraphQLString}
            },
            async resolve(parent, args) {
                var result = await DB.update('nav', {"_id":DB.getObjectId(args._id)},{title:args.title,
                    url:args.url,
                    sort:args.sort,
                    status:args.status,
                    add_time:new Date().getTime()
                });

                // console.log(result);
                return {
                    _id:args._id,
                    title:args.title,
                    url:args.url,
                    sort:args.sort,
                    status:args.status,
                    add_time:new Date().getTime()
                }
            }

        }    
        ,
        deleteNav:{
            type:NavSchema,
            args:{
                _id:{type: new GraphQLNonNull(GraphQLString)},
            },
            async resolve(parent, args) {

                var oneNavList = await DB.find('nav', { "_id": DB.getObjectId(args._id)});
              
                var deleteResult = await DB.remove('nav', {"_id":DB.getObjectId(args._id)});

                console.log(deleteResult.result.n);

                if(deleteResult.result.n){
                    return oneNavList[0];  
                }else{
                    return {}
                }

            }

        }  
    }
})

// 掛載到GraphQLSchema
module.exports=new GraphQLSchema({
    // query:RootSchema,
    mutation:MutationSchema
})
  • 新增

可以看到必填字段不填會(huì)提示

再次查詢列表

  • 修改
  • 刪除

五、Vue中使用GraphQl

5.1 使用graphQl簡(jiǎn)單查詢

安裝

  1. 找到Vue中集成GraphQl的文檔
  1. 安裝相應(yīng)的模塊

ApolloBoost是一種零配置開始使用ApolloClient的方式竿屹。它包含一些實(shí)用的默認(rèn)值报强,例如我們推薦的InMemoryCache和HttpLink,它非常適合用于快速啟動(dòng)開發(fā)拱燃。將它與vue-apollo和graphql一起安裝:

npm install vue-apollo graphql apollo-boost --save
  1. src/main.js中引入apollo-boost模塊并實(shí)例化ApolloClient
import ApolloClient from'apollo-boost'

const apolloClient = newApolloClient({
    //你需要在這里使用絕對(duì)路徑
    uri:'http://118.123.14.36:3002/graphql'
})

可以打開 http://118.123.14.36:3002/graphql 在控制臺(tái)查看查詢結(jié)果

  1. src/main.js配置vue-apollo插件
import VueApollofrom'vue-apollo'

Vue.use(VueApollo);
  1. 創(chuàng)建Apollo provider

Provider保存了可以在接下來(lái)被所有子組件使用的Apollo客戶端實(shí)例

const apolloProvider = newVueApollo({
    defaultClient:apolloClient
})

使用apollo Provider選項(xiàng)將它添加到你的應(yīng)用程序

new Vue({
    el:'#app',
    apolloProvider,
    render:h=>h(App)
})

簡(jiǎn)單查詢

組件加載的時(shí)候就會(huì)去服務(wù)器請(qǐng)求數(shù)據(jù)秉溉,請(qǐng)求的數(shù)據(jù)會(huì)放在navList這個(gè)屬性上面,在模板中可以直接使用當(dāng)前屬性

簡(jiǎn)單查詢文檔

帶參數(shù)查詢參考

import gql from'graphql-tag';

export default{ 
    data(){
        return { msg: '我是一個(gè) home 組件' } 
    },
    apollo: {
        // 簡(jiǎn)單的查詢扼雏,將更新 'hello' 這個(gè) vue 屬性 
        navList: gql`query { 
            navList { 
                title
            } 
        }` 
    },
}

另一種寫法:

import gql from 'graphql-tag'; 
export default{ 
    data(){
        return { 
            msg:'我是一個(gè) home 組件' 
        } 
    },
    // Apollo 具體選項(xiàng)
    apollo: {
        // // 帶參數(shù)的查詢
        // ping: {
        //     // gql 查詢
        //     query: gql`query PingMessage($message: String!) {
        //     ping(message: $message)
        //     }`,
        //     // 靜態(tài)參數(shù)
        //     variables: {
        //     message: 'Meow',
        //     },
        // },
    },
    apollo: { 
        // 注意方法名稱 和 查詢的名稱對(duì)應(yīng) 
        navList(){ 
            return { 
                query:gql`query { 
                    navList { 
                        title
                     } 
                }` 
            } 
        } 
    } 
}

完整例子

<template>
  <div class="news">
    <h1>{{ msg }}</h1> 
    <ul>
      <li v-for="(item,index) of navList" :key="index">
          {{item.title}}
      </li>      
    </ul>
    <br>
    <hr>
    <br>
    <ul>
      <li v-for="(item, index) of articleList" :key="index">
          {{item.title}}---{{item.status}}--{{item._id}}
      </li>      
    </ul>
  </div>
</template>

<script>
  import gql from 'graphql-tag';
  export default {
    name: 'app',
    data(){
      return{

        msg:'我是一個(gè)首頁(yè)頁(yè)面'

      }
    },
    apollo: {
      // 簡(jiǎn)單的查詢坚嗜,將更新 'hello' 這個(gè) vue 屬性
      navList: gql`{
         navList{
            title
          }
      }`,
      articleList:gql`{
         articleList{
            title,
            status,
            _id
          }
      }`
    }
 
  }
</script>

高級(jí)查詢

高級(jí)查詢文檔

  <div class="news">
    <h1>{{ msg }}</h1>    


    <ul>

      <li v-for="(item,key) of articleList" v-bind:key="key">
          {{item.title}}---{{item.status}}
      </li>      
    </ul>

    <button @click="getData()">
      點(diǎn)擊按鈕觸發(fā)事件請(qǐng)求graphQl接口
    </button>

    {{navList}}

  </div>

邏輯

import gql from 'graphql-tag';

  var navListGql=gql`{
        navList{
            title           
        }
   }`;

  export default {
    name: 'app',
    data(){
      return{
        msg:'我是一個(gè)新聞頁(yè)面',
        navList:[]

      }
    },
    apollo:{
      // articleList:gql`{
      //        articleList{
      //         title,
      //         status
      //       }

      // }`
        // 把請(qǐng)求的數(shù)據(jù)賦值給articleList
        articleList:{
          query:gql`query articleList($page:Int!,$pageSize:Int!){
                articleList(page:$page,pageSize:$pageSize){
                  title,
                  status
                }
          }`,
          variables:{
            page:2,
            pageSize:10
          }
        }
    },
    methods:{
      getData(){
        this.$apollo.addSmartQuery('navList',{          
            query:navListGql,
            result(response){
              console.log(response);
            },error(err){
              console.log(err);
            }
        })
      }
    }
  }

Vue GraphQl 傳參查詢

<template>
  <div class="article">
    <h1>{{ msg }}</h1>   
    <button @click="getData()">獲取文章數(shù)據(jù)</button>
   <ul>
      <li v-for="(item,key) of articleList" v-bind:key="key">
          {{item.title}}
      </li>      
    </ul>

  </div>
</template>

<script>
  import gql from 'graphql-tag';

  var articleListGql=gql`query articleList($page:Int!,$pageSize:Int!){
       articleList(page:$page,pageSize:$pageSize){
        title       
      }
  }`;
  export default {
    name: 'app',
    data(){
      return{
        msg:'article頁(yè)面',
        articleList:[]

      }
    },
    methods:{
      getData(){
        this.$apollo.addSmartQuery('articleList',{
          query:articleListGql,
          variables:{
            page:2,
            pageSize:8
          },
          result(response){
            console.log(response)
          },error(err){
            console.log(err)
          }
        })
      }
    }
  }
</script>

5.2 使用graphQl增加修改刪除

詳情文檔參考

服務(wù)器端接口

<template>
  <div class="news">
    <h1>導(dǎo)航的增加修改刪除</h1> 
    <div class="navForm">

        導(dǎo)航名稱:<input v-model="navJson.title" type="text" /> <br><br>
        導(dǎo)航鏈接: <input v-model="navJson.url" type="text" /><br><br>

        <button @click="doAdd()">提交數(shù)據(jù)</button>
        <button @click="doEdit()">修改數(shù)據(jù)</button>
        <button @click="doDele()">刪除數(shù)據(jù)</button>
    </div>


  </div>
</template>

<script>
  import gql from 'graphql-tag';

  var navMutationAddGql=gql`mutation($title:String!,$url:String!){
    addNav(title:$title,url:$url){
      title
    }
  }`;

  var navMutationEditGql=gql`mutation($id:String!,$title:String!,$url:String!){
    editNav(_id:$id,title:$title,url:$url){
      title
    }
  }`;

  var navMutationDelGql=gql`mutation($id:String!){
    deleteNav(_id:$id){
      title
    }
  }`;

  export default {
    name: 'app',
    data(){
      return{
        navJson:{
          title:"",
          url:""
        }
      }
    },
    methods:{
      // 提交表單
      doAdd(){
          // eslint-disable-next-line no-console
          console.log(this.navJson.title);

        this.$apollo.mutate({
            mutation:navMutationAddGql,
            variables: {
            title: this.navJson.title,
            url:this.navJson.url,
            }
        }).then((response)=>{
            console.log(response);
        }).catch((err)=>{
            console.log(err);
        })
      },
      // 修改數(shù)據(jù)
      doEdit(){
        this.$apollo.mutate({
          mutation:navMutationEditGql,
          variables: {
            id:"62beaf16323cb708d06580ce",
            title: this.navJson.title,
            url:this.navJson.url,
          }
        }).then((response)=>{
          console.log(response);
        }).catch((err)=>{
          console.log(err);
        })
      },
      doDele(){
        this.$apollo.mutate({
          mutation:navMutationDelGql,
          variables: {
            id:"62beaf50323cb708d06580d0",
          }
        }).then((response)=>{
          console.log(response);
        }).catch((err)=>{
          console.log(err);
        })
      }
    }
 
  }
</script>

可以看到新增成功效果

5.3 上拉分頁(yè)加載更多

npm i vue-infinite-scroll -S
// main.js配置 

//配置上拉分頁(yè)加載更多
var infiniteScroll =  require('vue-infinite-scroll');
Vue.use(infiniteScroll);

方法1:數(shù)據(jù)拼接

<template>
  <div class="article">
    <h1>{{ msg }}</h1>

    <div v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10">
      <ul>
        <li v-for="(item,key) of articleListData" v-bind:key="key">{{item.title}}</li>
      </ul>
    </div>
  </div>
</template>

<script>
import gql from "graphql-tag";

var articleListGql = gql`
  query articleList($page: Int!, $pageSize: Int!) {
    articleList(page: $page, pageSize: $pageSize) {
      title
    }
  }
`;

export default {
  name: "app",
  data() {
    return {
      msg: "上拉分頁(yè)加載更多",
      articleList: [],
      articleListData: [] /*實(shí)際要循環(huán)的數(shù)據(jù)*/,

      page: 1,
      busy: false
    };
  },
  methods: {
    loadMore() {
      this.$apollo.addSmartQuery("articleList", {
        query: articleListGql,

        variables: {
          page: this.page,
          pageSize: 8
        },

        result(response) {
          console.log(response);

          this.articleListData = this.articleListData.concat(
            response.data.articleList
          );

          this.page++;

          if (response.data.articleList < 8) {
            this.busy = true; //沒有數(shù)據(jù)禁用上拉分頁(yè)加載更多
          }
        },
        error(err) {
          console.log(err);
        }
      });
    }
  }
};
</script>

<style scoped>
  li {
    line-height: 4;
  }
</style>

方法2:使用 fetchMore 實(shí)現(xiàn)分頁(yè)(推薦)

https://vue-apollo.netlify.app/zh-cn/guide/apollo/pagination.html

<template>
  <div class="article">
    <h1>{{ msg }}</h1>

    <div v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10">
      <ul>
        <li v-for="(item,key) of articleList" v-bind:key="key">{{item.title}}</li>
      </ul>
    </div>
  </div>
</template>

<script>
import gql from "graphql-tag";

var articleListGql = gql`
  query articleList($page: Int!, $pageSize: Int!) {
    articleList(page: $page, pageSize: $pageSize) {
      title
    }
  }
`;

export default {
  name: "app",
  data() {
    return {
      msg: "上拉分頁(yè)加載更多",
      articleList: [],
      page: 1,
      busy: false
    };
  },
  apollo: {
    articleList() {
      return {
        // GraphQL 查詢
        query: articleListGql,
        // 初始變量
        variables: {
          page: this.page,
          pageSize: 5
        }
      };
    }
  },
  methods: {
    loadMore() {
      this.page++;

      this.$apollo.queries.articleList.fetchMore({
        // 新的變量
        variables: {
          page: this.page,
          pageSize: 5
        },
        // 用新數(shù)據(jù)轉(zhuǎn)換之前的結(jié)果
        updateQuery: (previousResult, { fetchMoreResult }) => {
          // eslint-disable-next-line no-console
          console.log(fetchMoreResult);
          return {
            articleList: [
              ...previousResult.articleList,
              ...fetchMoreResult.articleList
            ]
          };
        }
      });
    }
  }
};
</script>

<style scoped>
li {
  line-height: 4;
}
</style>

分頁(yè)效果

項(xiàng)目例子完整代碼下載地址 https://blog.poetries.top/assets/graphql-code.zip

六夯膀、文檔

本文由mdnice多平臺(tái)發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诗充,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诱建,更是在濱河造成了極大的恐慌蝴蜓,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異茎匠,居然都是意外死亡格仲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門诵冒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凯肋,“玉大人,你說我怎么就攤上這事汽馋∥甓” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵豹芯,是天一觀的道長(zhǎng)悄雅。 經(jīng)常有香客問我,道長(zhǎng)铁蹈,這世上最難降的妖魔是什么宽闲? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮握牧,結(jié)果婚禮上容诬,老公的妹妹穿的比我還像新娘。我一直安慰自己我碟,他們只是感情好放案,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著矫俺,像睡著了一般吱殉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上厘托,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天友雳,我揣著相機(jī)與錄音,去河邊找鬼铅匹。 笑死押赊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的包斑。 我是一名探鬼主播流礁,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼罗丰!你這毒婦竟也來(lái)了神帅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤萌抵,失蹤者是張志新(化名)和其女友劉穎找御,沒想到半個(gè)月后元镀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霎桅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年栖疑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滔驶。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遇革,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揭糕,到底是詐尸還是另有隱情澳淑,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布插佛,位于F島的核電站杠巡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雇寇。R本人自食惡果不足惜氢拥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锨侯。 院中可真熱鬧嫩海,春花似錦、人聲如沸囚痴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)深滚。三九已至奕谭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間痴荐,已是汗流浹背血柳。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留生兆,地道東北人难捌。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鸦难,于是被迫代替她去往敵國(guó)和親根吁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容