使用Nuxt.js + apollo-module(vue-apollo) + TypeScript的方式给代码添加类型
我认为在使用Vue.js开发GraphQL时,Vue Apollo是一个选择。
GraphQL的客户端可以使用fetch或XMLHttpRequest进行实现。然而,考虑到在Nuxt.js上进行SSR,我们需要考虑一些要点和实现方面的问题,比如在服务器端获取数据,与Vue的data进行合并等等。使用Vue Apollo可以更轻松地处理这些方面的问题。
然而,Apollo的高功能需要一些时间来掌握,而且根据使用情况可能会出现超标的可能性。
Vue Apollo是Vue.js的插件,可以将Apollo的GraphQL客户端整合到Vue.js中。
此外,Nuxt.js的apollo-module将Vue Apollo集成到了Nuxt.js中。
在这篇文章中,我将解释在使用Nuxt.js + TypeScript + apollo-module进行开发时如何添加类型注解。
顺便提一下,在这篇文章中使用的软件版本如下所示。
-
- nuxt: 2.12.0
vue: 2.6.11
typescript: 3.8.3
nuxtjs/apollo: 4.0.0-rc19
vue-apollo: 3.0.3
另外,本文的示范代码也在下面的仓库中公开。
安装
使用以下命令创建Nuxt项目。
npx create-nuxt-app nuxt-apollo-typescript
如果设置处于设置中,编程语言为TypeScript,渲染模式为Universal(SSR),其余可以随意。
使用fetch进行的GraphQL客户端操作
首先,由于我想从简单的地方开始,所以我要尝试使用fetch创建一个最小化的GraphQL客户端。我将使用常用的SW API作为GraphQL API的示例。
<template>
<div :class="$style.container">
<h1>Star wars films</h1>
<ul>
<li v-for="film in films" :key="film.episodeID">
{{ film.title }}
</li>
</ul>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
// GraphQLのレスポンスに型をつける
interface Film {
episodeID: number
title: string
}
interface Edge {
node: Film
}
interface FilmConnection {
edges: Edge[]
}
interface ResponseData {
allFilms: FilmConnection
}
interface Response {
data: ResponseData
}
// Vueのdataに型をつける
interface VueData {
films: Film[]
}
export default Vue.extend({
data(): VueData {
return {
films: []
}
},
async created() {
const query = `
{
allFilms(first: 3) {
edges {
node {
episodeID
title
}
}
}
}
`
const res = await fetch(
'https://swapi-graphql.netlify.com/.netlify/functions/index',
{
method: 'POST',
body: JSON.stringify({
query
}),
headers: {
'Content-Type': 'application/json'
}
}
).then<Response>((res) => res.json())
this.films = res.data.allFilms.edges.map((e) => e.node)
}
})
</script>
<style module>
.container {
margin: 10px;
}
</style>
如果成功的话,将显示如下所示的标题列表。
然而,这段代码还存在一些令人遗憾的地方。由于fetch是浏览器的API,因此在SSR时尝试获取数据会导致错误(将created更改为asyncData,可以再现SSR时的错误)。
在解决这个问题时,有一个方案是使用node-fetch,但在这里我们尝试使用Nuxt的apollo-module来解决。
安装apollo-module
首先,按照官方安装指南,安装apollo-module模块。
yarn add @nuxtjs/apollo graphql-tag
在nuxt的配置文件中加载模块,并写入最基本的配置。
modules: ['@nuxtjs/apollo'],
apollo: {
clientConfigs: {
default: {
httpEndpoint:
'https://swapi-graphql.netlify.com/.netlify/functions/index'
}
}
},
在这种情况下,如果将pages/index.vue的script改写如下,就可以使用Apollo来获取数据了!
<script>
import gql from 'graphql-tag'
export default {
computed: {
films() {
return this.allFilms.edges.map((e) => e.node)
}
},
apollo: {
allFilms: gql`
query {
allFilms(first: 3) {
edges {
node {
episodeID
title
}
}
}
}
`
}
}
</script>
在Vue组件的定义中,添加一个名为apollo的属性,在这里编写与GraphQL API相关的查询,响应将合并到data中。
TypeScript的类型定义
目前,我們可以通過 Apollo 來獲取數據,但是它並沒有 TypeScript 的類型支持。因此,當我們嘗試使用 lang=”ts” 和 Vue.extend() 為其添加類型時,會出現編譯錯誤。
<script lang="ts">
import Vue from 'vue'
import gql from 'graphql-tag'
export default Vue.extend({
computed: {
films() {
return this.allFilms.edges.map((e) => e.node)
}
},
apollo: {
allFilms: gql`
query {
allFilms(first: 3) {
edges {
node {
episodeID
title
}
}
}
}
`
}
})
</script>
编译错误:
No overload matches this call.
The last overload gave the following error.
Argument of type '{ computed: { films(): any; }; apollo: { allFilms: DocumentNode; }; }' is not assignable to parameter of type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>'.
Object literal may only specify known properties, and 'apollo' does not exist in type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<...>>'.
这是因为在Vue的Options API中不存在apollo字段。因此,让我们修改tsconfig.json文件,以便在Vue的Options API中可以定义apollo。
"compilerOptions": {
"types": [
"@types/node",
"@nuxt/types",
+ "vue-apollo"
]
}
通过这样做,当TypeScript进行编译时,将会加载Vue Apollo所定义的Apollo类型定义,从而赋予Apollo类型。
只要修正一下this.allFilms的类型,编译就能通过了。
<script lang="ts">
import Vue from 'vue'
import gql from 'graphql-tag'
// GraphQLのレスポンスに型をつける
interface Film {
episodeID: number
title: string
}
interface Edge {
node: Film
}
interface FilmConnection {
edges: Edge[]
}
// Vueのdataに型をつける
interface Data {
allFilms: FilmConnection
}
export default Vue.extend({
data(): Data {
return {
allFilms: {
edges: []
}
}
},
computed: {
films(): Film[] {
return this.allFilms.edges.map((e) => e.node)
}
},
...
从模式生成响应类型
目前,我们手动编写像FilmConnection这样的类型,但希望能从GraphQL的模式中自动生成这些类型。因此,首先从以下网址获取模式。
将这个文件以 `schema.graphql` 的名称放置在项目中。接下来,使用 GraphQL Code Generator 从此模式生成 TypeScript 类型。
首先需要安装库。
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript
接下来是配置文件。
overwrite: true
schema: "schema.graphql"
generates:
lib/GraphQL/generated.ts:
plugins:
- "typescript"
当准备好后,输入命令。
yarn run graphql-codegen --config codegen.yml
如果顺利的话,应该能够得到以下类似的文件。
export type Maybe<T> = T | null;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
};
...
只需要将这个导入并使用即可。因为类型定义稍有变化,所以代码也进行了相应的修改。
<script lang="ts">
import Vue from 'vue'
import gql from 'graphql-tag'
import { FilmsConnection, Film } from '~/lib/GraphQL/generated'
interface Data {
allFilms?: FilmsConnection
}
export default Vue.extend({
data(): Data {
return {
allFilms: undefined
}
},
computed: {
films(): Film[] {
if (this.allFilms == null || this.allFilms.edges == null) return []
return this.allFilms.edges
.map((e) => e?.node)
.filter((f): f is Film => f != null)
}
},
apollo: {
allFilms: gql`
query {
allFilms(first: 3) {
edges {
node {
episodeID
title
}
}
}
}
`
}
})
</script>
通过这个方法,lang=”ts” 的Vue组件现在可以使用apollo,并且还能给GraphQL的响应加上类型。