发布于 2026-01-06 3 阅读
0

使用 Apollo Client 封装 REST API 调用:一种“自己动手”的方法

使用 Apollo Client 封装 REST API 调用:一种“自己动手”的方法

有时,当你的应用正在从 REST API 迁移到 GraphQL API 时,你可能会遇到需要的数据分散在两个 API 中的情况。假设你之前从 REST API 获取数据时,会将其存储在应用的全局状态中——无论是 Redux、MobX 还是 Vuex。但有了全新的 GraphQL API,你甚至无需费心编写存储响应的样板代码——Apollo Client 会为你处理这一切!这是否意味着有了两个 API,你就必须坚持使用老旧但有效的解决方案,放弃 Apollo Client 缓存呢?当然不是!

您可以使用Apollo 封装 REST API 调用,并将结果存储在 Apollo 缓存中。如果您的应用程序规模较大且包含多个 API,则可以使用apollo-link-rest库来实现此功能。本文将提供一个基本的 DIY 方案,帮助您更好地理解 Apollo 解析器的工作原理以及如何在应用程序中有效地使用它们。

我们要建造什么?

例如,我们将使用基于Rick and Morty API构建的 Vue 单页应用程序。这个 API 的优点在于它同时具有REST 和 GraphQL 端点,因此我们可以对其进行一些实验。

假设我们的应用程序完全使用 REST API。那么,在前端,我们有一个Vuex store,并通过 Vuex actions 调用查询来从 APIaxios获取角色剧集信息。

// Vuex state

state: {
  episodes: [],
  characters: [],
  favoriteCharacters: [],
  isLoading: false,
  error: null
},
Enter fullscreen mode Exit fullscreen mode
// Vuex actions

actions: {
  getEpisodes({ commit }) {
    commit('toggleLoading', true);
    axios
      .get('/episode')
      .then(res => commit('setEpisodes', res.data.results))
      .catch(err => commit('setError', error))
      .finally(() => commit('toggleLoading', false));
  },
  getCharacters({ commit }) {
    commit('toggleLoading', true);
    axios
      .get('/character')
      .then(res => commit('setCharacters', res.data.results))
      .catch(err => commit('setError', err))
      .finally(() => commit('toggleLoading', false));
  },
  addToFavorites({ commit }, character) {
    commit('addToFavorites', character);
  },
  removeFromFavorites({ commit }, characterId) {
    commit('removeFromFavorites', characterId);
  }
}
Enter fullscreen mode Exit fullscreen mode

这里我不列出 Vuex mutations,因为它们非常直观——我们将获取的字符分配给state.characters等等。

如您所见,我们需要手动处理加载标志,并在出现问题时存储错误信息。

数组中的每个字符characters都是一个对象:

角色对象

现在假设我们的后端开发人员创建了一个查询来获取剧集,但角色信息仍然需要通过 REST API 获取。那么,我们该如何处理这个问题呢?

步骤 1:扩展 GraphQL schema

在 GraphQL 中,任何可以从端点获取的数据都必须具有类型,并且必须在 GraphQL schema 中定义。为了保持一致性,我们characters也应该向 schema 中添加类型。“但是怎么做呢?”你可能会问,“schema 不是在后端定义的吗!”没错,但我们也可以在前端扩展schema stitching这个 schema!这个过程称为类型定义。虽然这一步完全是可选的,但我仍然建议始终为你的实体定义 GraphQL 类型定义,即使是本地实体也不例外。如果你使用代码生成器从 GraphQL schema 创建 TypeScript 类型,这将对你有所帮助;此外,如果你在 IDE 中使用Apollo 插件,它还能启用验证和自动完成功能。

让我们创建一个新的字符类型。我们将使用它graphql-tag来将字符串解析为 GraphQL 类型:

// client.js

import gql from "graphql-tag";

const typeDefs = gql`
  type Character {
    id: ID!
    name: String
    location: String
    image: String
  }
`;
Enter fullscreen mode Exit fullscreen mode

如您所见,这里我们并没有使用character对象中的所有字段,而只使用了我们需要的字段。

现在我们还需要Query使用 GraphQLcharacters查询来扩展类型:

// client.js

import gql from "graphql-tag";

const typeDefs = gql`
  type Character {
    id: ID!
    name: String
    location: String
    image: String
  }
  extend type Query {
    characters: [Character]
  }
`;
Enter fullscreen mode Exit fullscreen mode

为了将这部分模式与从 GraphQL 端点获取的模式拼接起来,我们需要将以下typeDefs选项传递给 GraphQL 客户端:

// client.js

import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import gql from "graphql-tag";

const httpLink = createHttpLink({
  uri: "https://rickandmortyapi.com/graphql"
});

const cache = new InMemoryCache();

const typeDefs = gql`
  type Character {
    id: ID!
    name: String
    location: String
    image: String
  }
  extend type Query {
    characters: [Character]
  }
`;

export const apolloClient = new ApolloClient({
  link: httpLink,
  cache,
  typeDefs
});
Enter fullscreen mode Exit fullscreen mode

步骤 2:编写查询和解析器

我们需要定义一个带有@client指令的 GraphQL 查询,以便在需要获取角色数据时调用。@client该指令告诉 Apollo Client 不要从 GraphQL 端点获取数据,而是从本地缓存获取。通常,我会将查询语句保存在.gql文件中,并在 webpack 配置中添加一个指令graphql-tag/loader以便导入它们。

// characters.query.gql

query Characters {
  characters @client {
    id
    name
    location
    image
  }
}
Enter fullscreen mode Exit fullscreen mode

但这里有个问题:本地缓存里没有字符!我们该如何“告诉”Apollo Client它从哪里获取这些数据呢?为此,我们需要编写一个解析器。每次我们尝试获取字符并在应用程序中渲染它们时,都会调用这个解析器。

让我们创建一个解析器对象,并为characters查询定义一个解析器。

// client.js

const resolvers = {
  Query: {
    characters() {
      ...
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

这里我们该怎么办?我们需要执行与 Vuex action 中相同的 axios 调用!我们将响应字段映射到 GraphQL 类型字段,以使结构更清晰:

// client.js

const resolvers = {
  Query: {
    characters() {
      return axios.get("/character").then(res =>
        res.data.results.map(char => ({
          __typename: "Character",
          id: char.id,
          name: char.name,
          location: char.location.name,
          image: char.image
        }))
      );
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

搞定!现在,当我们调用 GraphQLcharacters查询时,解析器会执行 REST API 调用并返回结果。额外提示:$apollo.queries.characters.loadingREST API 调用进行时,属性会相应更新!此外,如果此调用发生错误,Apollo 查询error钩子也会被触发。

结论

如您所见,将部分 API 放在 REST 端点上并不会妨碍您使用 Apollo Client 及其缓存。任何 REST API 调用都可以使用 Apollo 解析器进行封装​​,其结果可以存储到 Apollo 缓存中,从而简化迁移过程。

文章来源:https://dev.to/n_tepluhina/wrapping-rest-api-calls-with-apollo-client-do-it-yourself-approach-4i3p