nest.js和graphql的亲密结合(二) ——客户端

1,632 阅读3分钟

上一篇的服务端项目搭建,我们开始下一阶段的客户端搭建。这次文章中主要介绍的是使用vue-apollo整合apollo-clientVue


代码仓库

1. 简介

什么是apollo-client

apollo-client是一个整合Vue、React和其他市面上比较流行的Graphql的客户端。

什么是vue-apollo

vue-apollo 通过声明式查询将apollo-client整合到你的Vue组件中。写这篇文章的时候,该插件兼容 Vue2Vue3还在开发中。

2. 创建客户端工程

vue create vue-graphql
cd vue-graphql
npm install --save vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag
// ps: 文中所用工程使用到了element-ui、vue-router,这两者的安装过程就不多说了。
1.安装插件
import VueApollo from 'vue-apollo'
Vue.use(VueApollo)
2.创建apollo-link-http
import { createHttpLink } from 'apollo-link-http';
const httpLink = createHttpLink({
    uri: 'http://localhost:3000/graphql', // 指定后端应用的路径,可以是函数或字符串。默认值为 /graphql
});
3.创建apollo-link
import { ApolloLink } from 'apollo-link';
const middlewareLink = new ApolloLink((operation, forward) => {
    const token = localStorage.getItem('token'); // 在这里进行token的注入
    operation.setContext({
        headers: {
            Authorization: token || null
        }
    });
    return forward(operation);
});
4.创建apollo客户端
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';

const apolloClient = new ApolloClient({
    link: middlewareLink.concat(httpLink),
    cache: new InMemoryCache(),
    connectToDevTools: true
});
5.创建Vue-apollo 实例
import VueApollo from 'vue-apollo'

const apolloProvider = new VueApollo({
    defaultClient: apolloClient
})

new Vue({
  render: h => h(App),
  router,
  apolloProvider // 注入到vue实例
}).$mount('#app')

并将apolloProvide注册到Vue实例中。

new Vue({
  el: '#app',
  // 像 vue-router 或 vuex 一样注入 apolloProvider,可以通过this.$apollo访问插件实例
  apolloProvider,
  render: h => h(App),
})
6.Vscode安装 Apollo GraphQL 扩展

安装vscode插件可以使开发者在开发Graphql应用时,更加的便捷。其主要功能如下:

  • 在graphql文件和js文件中高亮graphql语法
  • 代码的智能提示,语法优化提示
  • 校验语法正确
  • More.....

在项目的根目录下添加apollo graphql的拓展文件

// apollo.config.js
module.exports = {
    client: {
        service: {
            name: 'my-app',
            // GraphQL API 的 URL
            url: 'http://localhost:3000/graphql'
        },
        // 通过扩展名选择需要处理的文件
        includes: [
            'src/**/*.vue',
            'src/**/*.js'
        ]
    }
};

完成以上步骤后,能够完成我们基本的开发需求。

3. 使用

1. 查询

服务端代码

// src/cats/cats.graphql
type Query {
  cats: [Cat] // 批量查询
  cat(id: ID!): Cat // 单个查询
}
  @Query('cats') // 声明query。与graphql文件中的query对应
  @UseGuards(CatsGuard) // 这个注解是nestjs内置的注解。用于权限校验
  async getCats() {
    return this.catsService.findAll();
  }

  @Query('cat') // 声明query。与graphql文件中的query对应
  async findOneById(
    @Args('id', ParseIntPipe)
    id: number,
  ): Promise<Cat> {
    return this.catsService.findOneById(id);
  }

客户端代码


<script>
import gql from 'graphql-tag';
export default {
    data() {
        return {
            cats: [],
            catList: []
        }
    },
    apollo: {
        // 当查询的名字和apollo的属性名一致
        cats: gql`
            query {
                cats {
                    id,
                    name,
                    age
                }
            }
        `,
        // 当查询的属性名和apollo的属性名不一致时
        catList: {
            query: gql`
                query {
                    cats {
                        id,
                        name,
                        age
                    }
                }
            `,
            update: (data) => data.cats // 标识vue-apollo怎么处理数据
        }
    }
};
</script>

注意:当查询的属性名和query的字段不一致时,vue-apollo不会去把返回的数据直接塞入到对应的属性名中,而是需要我们使用update属性去处理。正如上述的代码中所描述的。

2. 变更
    methods: {
        createDataButtonClick() {
            this.addCat() // 调用mutation方法
        },
        async addCat() {
            await this.$apollo.mutate({
                mutation: gql`
                    mutation($createCatInput: CreateCatInput) {
                        createCat(createCatInput: $createCatInput) {
                            name,
                            age
                        }
                    }
                `,
                variables: {
                    createCatInput: {
                        name: "cat2",
                        age: 1
                    }
                }
            })
        }
    }
3. 订阅
1. 安装依赖
yarn add apollo-link-ws apollo-utilities subscriptions-transport-ws
2. vue-apollo初始化修改
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws'
import { InMemoryCache } from 'apollo-cache-inmemory';
// import { ApolloLink } from 'apollo-link';
import { split } from 'apollo-link'
import { getMainDefinition } from 'apollo-utilities'


const httpLink = new HttpLink({
  uri: 'http://localhost:3000/graphql',
  // 对于token的校验可以改为headers,或者fetch
})

// 创建订阅的 websocket 连接
const wsLink = new WebSocketLink({
  uri: 'ws://localhost:3000/subscriptions',
  options: {
    reconnect: true,
  },
})
const link = split(
  // 根据操作类型分割
  ({ query }) => {
    const definition = getMainDefinition(query)
    return definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
  },
  wsLink,
  httpLink
)

// 创建 apollo 客户端
const apolloClient = new ApolloClient({
  // link: middlewareLink.concat(httpLink).concat(),
  link,
  cache: new InMemoryCache(),
  connectToDevTools: true
});

const apolloProvider = new VueApollo({
  defaultClient: apolloClient
})

new Vue({
  render: h => h(App),
  router,
  apolloProvider
}).$mount('#app')
3. 服务端声明订阅代码

订阅主要使用websocket进行传输。客户端注册回调函数,服务端注册和发布订阅。

// src/cats/cat.graphql
type Subscription {
  catCreated: Cat
}
// src/cats/cat.resolver.ts
import { PubSub } from 'graphql-subscriptions';
.....
@Mutation('createCat')
async create(@Args('createCatInput') args: CreateCatDto): Promise<Cat> {
    const createdCat = await this.catsService.create(args);
    pubSub.publish('catCreated', { catCreated: createdCat }); // 添加订阅
    return createdCat;
}
.....
@Subscription('catCreated')
catCreated() {
    return pubSub.asyncIterator('catCreated'); // 发布订阅
}

文章汇总