试着使用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。

undefined
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>

做完了!

undefined
undefined

在使用GraphQL时需要注意的事项

在引入GraphQL时,应该考虑以下细节以获取GraphQL的好处,并记录相应的注意事项和对策。

以下是一些在上述文章中提到的问题的示例:

    • データを取得する際、N+1問題が起こる場合がある

 

    • クエリが複雑になるとリクエストボディが肥大化する

 

    • POSTを利用するためHTTPキャッシュに乗らない

 

    などがあります。

总结

使用GraphQL时,您可以编写一次查询以获取所需的相关数据,然后将其发布。与RESTful API相比,这将减少API调用次数,并可以提高服务性能。

一方面,如果只是愚蠢地实施,可能无法充分享受GraphQL的好处,所以我希望今后能够不断学习。

广告
将在 10 秒后关闭
bannerAds