试着使用GraphQL与Vue3获取宝可梦的数据
综述
在我们目前开发的服务要求中,对于数据获取的方法,除了RESTful API外,还有一种选择是GraphQL。
在那个时候,我对GraphQL的理解就像返回JSON(类似的东西?)的API那样有点模糊,所以我觉得这样不行,于是在年末年初时稍微尝试了一下。
开发环境
-
- yarn 1.22.19
-
- Typescript 4.9.3
-
- Vue.js 3.2.45
-
- vite 4.0.0
-
- graphql 16.6.0
- etc…
GraphQL是一种查询语言
GraphQL的公式
GraphQL是一种用于API的查询语言。
在CircleCI的博客中,对GraphQL进行了入门指南的学习,总结为以下内容:GraphQL是什么?它的优势和概述。
GraphQL是一种设计用于从API中获取客户端应用程序所需数据的语言。
使用GraphQL,客户端应用程序可以从后端API获取所需数据的类型和形状。
无论请求的类型是什么,GraphQL都只有一个端点供客户端应用程序调用。
GraphQL与SQL非常相似,但在前端起作用。
我对此有点了解,但还不太了解它的实际应用。
作为GraphQL的对比对象,我们可以考虑RESTful API,接下来我会详细解释它们的区别。
与RESTful API的不同之处
休息的API
在RESTful API中,詳細內容可以在記載中找到。其中一個特點是需要使用URI、GET或POST等請求方法的相關資訊來指定要獲取的資源。
因此,要获取多个资源需要多个请求(例如URI).
以下展示了RESTful API中获取资源的流程。
我们正在获取图鉴编号为25的精灵的名称和种族值。
{
"id": 25,
"name": "Pikachu"
}
{
"hitPoints": 35,
"attack": 55,
"defense": 40,
"specialAttack": 50,
"specialDefense": 50,
"speed": 90
}
GraphQL 是一种用于API开发的查询语言。
接下来,我们将展示使用GraphQL获取相同数据的流程。
只有一个终端点,获取的资源由查询决定。
发表的查询如下所示。
query{
pokemon(id: 25) {
id
name
basestats {
hitPoints
attack
defense
specialAttack
specialDefense
speed
}
}
}
"pokemon" {
"id": 25,
"name": "Pikachu",
"basestats": {
"hitPoints": 35,
"attack": 55,
"defense": 40,
"specialAttack": 50,
"specialDefense": 50,
"speed": 90
}
}
在GraphQL API中,可以通过一次请求获取应用程序所需的所有数据。
在使用GraphQL时,我们需要创建一个查询,该查询可以一次获取所需的相关数据,并将其作为POST请求发送。
在上面的示例中,我们不是使用两个API调用,而是使用一个API请求来获取嵌套的JSON对象,其中包含了Pokemon和种族值。
在什么样的场合使用
如上所示,GraphQL的主要优势是
-
- クエリによって、欲しいデータのみを全て取得することができる
-
- その際のAPIへのリクエストは一度だけでよい
- エンドポイントが単一である
这就是说的。
如果涉及的数据很多,在RESTFul API中需要多次调用的情况下,考虑使用GraphQL可能会很有帮助。这样可以减少调用次数,并提高服务的性能。
使用Vue.js并借助Graphql实现数据获取和筛选搜索功能。
那么,我将尝试实际使用一下。
这些文章在这方面对我很有帮助。
连接GraphQL API到VueJS前端的方法。
本次作为练习,将获取并显示宝可梦的名称、类型和图片。
我们将使用Pokemon API作为GraphQL的端点。
https://graphql-pokemon2.vercel.app
附带提到,您可以在此找到用于练习的可使用的GraphQL API。
八个可供您项目和演示免费使用的GraphQL API。
type Attack {
name: String
type: String
damage: Int
}
type Pokemon {
id: ID!
number: String
name: String
weight: PokemonDimension
height: PokemonDimension
classification: String
types: [String]
resistant: [String]
attacks: PokemonAttack
weaknesses: [String]
fleeRate: Float
maxCP: Int
evolutions: [Pokemon]
evolutionRequirements: PokemonEvolutionRequirement
maxHP: Int
image: String
}
type PokemonAttack {
fast: [Attack]
special: [Attack]
}
type PokemonDimension {
minimum: String
maximum: String
}
type PokemonEvolutionRequirement {
amount: Int
name: String
}
type Query {
query: Query
pokemons(first: Int!): [Pokemon]
pokemon(id: String, name: String): Pokemon
}
我将根据这个进行实施。
首先,按照以下方式安装Apollo相关的软件包。
Apollo是一套用于在应用程序中使用GraphQL的工具集,也是一个社区项目。
Vue Apollo
yarn add vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag @vue/apollo-composable
最终的package.json如下所示。
{
"name": "vue-graphql",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@types/express": "^4.17.15",
"@vue/apollo-composable": "^4.0.0-beta.1",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.10",
"apollo-link": "^1.2.14",
"apollo-link-http": "^1.5.17",
"express": "^4.18.2",
"express-graphql": "^0.12.0",
"graphql": "^16.6.0",
"graphql-tag": "^2.12.6",
"vue": "^3.2.45",
"vue-apollo": "^3.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vue-tsc": "^1.0.11"
}
}
接下来,我们将准备使用GraphQL。
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import VueApollo from 'vue-apollo';
// GraphQL APIへの接続を確立
const httpLink = new HttpLink({
uri: 'https://graphql-pokemon2.vercel.app'
});
// ApolloClientインスタンス生成
export const apolloClient = new ApolloClient({
// データが正しいAPIからポーリングされるように設定
link: httpLink,
// キャッシュを渡す。InMemoryCacheはApolloClientのデフォルトのキャッシュ実装であるのでこれを使用
cache: new InMemoryCache(),
// Apollo Client Devtoolsを、Webブラウザのインスペクタに「Apollo」タブとして表示する
connectToDevTools: true
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
const app = createApp(App);
// VueApolloをvueで使用する
app.use(apolloProvider);
app.mount('#app');
我将先给这批获取的宝可梦数据进行类型定义。
export type Pokemon = {
name: string;
types: string[];
image: string;
}
从App.vue中加载搜索表单组件。
<script setup lang="ts">
import PokemonForm from './components/PokemonForm.vue';
</script>
<template>
<div>ポケモン検索したい!</div>
<pokemon-form />
</template>
我們將實現一個能夠使用寶可夢的英文名稱來篩選的搜索表單。
如果想在setup()之外使用useQuery(),需要使用provideApolloClient(apolloClient)。根据文档,这是因为无法通过Vue的提供/注入机制来注入客户端。在setup之外使用方法。
如果没有使用,将会发生以下错误:未捕获的错误:找不到ID为default的Apollo客户端。如果您在组件设置之外,请使用provideApolloClient()。
<template>
<form>
<input type="text" placeholder="Name" v-model="name">
<input type="button" value="検索" @click="(e) => {pokemonQuery(name, e)}">
</form>
<pokemon-list v-if="fetchdata" :pokemons="fetchdata.pokemons" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useQuery, provideApolloClient } from '@vue/apollo-composable';
import gql from "graphql-tag"
import { apolloClient } from '../main';
import PokemonList from "./PokemonList.vue"
import { Pokemon } from '../model/pokemon';
const name = ref<string>();
type Query = {
pokemons: Pokemon[]
} | undefined;
const fetchdata = ref<Query>();
// https://v4.apollo.vuejs.org/guide-composable/setup.html#usage-outside-of-setup
//
provideApolloClient(apolloClient);
const getPokemonByName = (name: string) => {
console.log("getpPokemonByName");
const query = gql`
query($name: String) {
pokemon(name: $name) {
name
types
image
}
}
`;
const {onResult} = useQuery<Query>(query, {
name
});
onResult(result => {
fetchdata.value = {pokemons: [result.data.pokemon]};
});
};
const getAllPokemons = () => {
const query = gql`
query {
pokemons(first: 150){
name
types
image
}
}
`;
const {onResult} = useQuery<Query>(query);
onResult(result => {
fetchdata.value = result.data;
});
};
const pokemonQuery = (name: string | undefined, event: MouseEvent) => {
event.preventDefault();
if (!name) {
getAllPokemons();
return;
};
getPokemonByName(name);
};
getAllPokemons();
</script>
在这种情况下,获取结果和更新fetchdata的过程如下执行。
const {onResult} = useQuery<Query>(query, {
name
});
onResult(result => {
fetchdata.value = {pokemons: [result.data.pokemon]};
});
需要注意的是,你也可以通过获取`result`属性来完成,但是无法更新`fetchdata`,它会变为`undefined`。这是因为使用`useQuery`进行数据获取是异步处理的,所以在数据获取完成之前就执行了赋值操作。
hogehoge() => {
const {result} = useQuery<Query>(query, {
name
});
fetchdata.value = {pokemons: [result.data.pokemon]};
}
下一步,我们将实现一个用于显示Pokemon列表的组件。
在初次显示时,我们会通过循环来逐个显示150只Pokemon。每个Pokemon都会被显示一次。
<script setup lang="ts">
import pokemonItem from './pokemonItem.vue';
import { Pokemon } from '../model/pokemon';
const {pokemons} = defineProps<{pokemons: Pokemon[]}>();
</script>
<template>
<div v-for="pokemon in pokemons">
<pokemon-item :pokemon="pokemon" />
</div>
</template>
最后我们将实现一个组件来显示一个特定种类的宝可梦。
由于类型是字符串数组,所以我们使用join()方法来连接并显示宝可梦类型。
<script setup lang="ts">
import { Pokemon } from '../model/pokemon';
const {pokemon} = defineProps<{pokemon: Pokemon}>();
</script>
<template>
<div>
<h3>{{ pokemon.name }}</h3>
<p>{{ pokemon.types.join("・") }}</p>
<img :src="pokemon.image" width="100">
</div>
</template>
做完了!
在使用GraphQL时需要注意的事项
在引入GraphQL时,应该考虑以下细节以获取GraphQL的好处,并记录相应的注意事项和对策。
以下是一些在上述文章中提到的问题的示例:
-
- データを取得する際、N+1問題が起こる場合がある
-
- クエリが複雑になるとリクエストボディが肥大化する
-
- POSTを利用するためHTTPキャッシュに乗らない
- などがあります。
总结
使用GraphQL时,您可以编写一次查询以获取所需的相关数据,然后将其发布。与RESTful API相比,这将减少API调用次数,并可以提高服务性能。
一方面,如果只是愚蠢地实施,可能无法充分享受GraphQL的好处,所以我希望今后能够不断学习。