跳至内容

本地状态

过时

本指南已过时,需要针对 Vue 3 和 vue-apollo 4 进行重写。欢迎贡献!

为什么要使用 Apollo 本地状态管理?

当您使用 Apollo 执行 GraphQL 查询时,API 调用的结果将存储在 **Apollo 缓存** 中。现在想象一下,您还需要存储某种本地应用程序状态,并使其可供不同的组件使用。通常,在 Vue 应用程序中,我们可以使用 Vuex 来实现这一点。但是,同时使用 Apollo 和 Vuex 意味着您将数据存储在两个不同的位置,因此您将拥有 *两个真相来源*。

好消息是 Apollo 有一种机制可以将本地应用程序数据存储到缓存中。以前,它使用 apollo-link-state 库来实现这一点。从 Apollo 2.5 版本开始,此功能已包含在 Apollo 核心库中。

创建本地模式

就像创建 GraphQL 模式是我们在服务器上定义数据模型的第一步一样,编写本地模式是我们客户端采取的第一步。

让我们创建一个本地模式来描述一个项目,该项目将作为待办事项列表的单个元素。此项目应包含一些文本,一些属性来定义它是否已完成,以及一个 ID 来区分不同的待办事项。因此,它应该是一个包含三个属性的对象

js
{
  id: 'uniqueId',
  text: 'some text',
  done: false
}

现在,我们准备将 Item 类型添加到我们的本地 GraphQL 模式中。

js
//main.js

import gql from 'graphql-tag';

export const typeDefs = gql`
  type Item {
    id: ID!
    text: String!
    done: Boolean!
  }
`;

gql 这里代表 JavaScript 模板字面量标签,它解析 GraphQL 查询字符串。

现在,我们需要将 typeDefs 添加到我们的 Apollo 客户端中。

js
// main.js

const apolloClient = new ApolloClient({
  typeDefs,
  resolvers: {},
});

警告

如您所见,我们还在这里添加了一个空的 resolvers 对象:如果我们不将其分配给 Apollo 客户端选项,它将无法识别对本地状态的查询,并尝试向远程 URL 发送请求。

在本地扩展远程 GraphQL 模式

您不仅可以从头开始创建本地模式,还可以将本地 **虚拟字段** 添加到您现有的远程模式中。这些字段仅存在于客户端,对于使用本地状态装饰服务器数据很有用。

想象一下,我们在远程模式中有一个 User 类型

js
type User {
  name: String!
  age: Int!
}

我们想将一个仅限本地的属性添加到 User

js
export const schema = gql`
  extend type User {
    twitter: String
  }
`;

现在,当查询用户时,我们需要指定 twitter 字段是本地的

js
const userQuery = gql`
  user {
    name
    age
    twitter @client
  }
`;

初始化 Apollo 缓存

要在您的应用程序中初始化 Apollo 缓存,您需要使用 InMemoryCache 构造函数。首先,让我们将其导入到您的主文件中

js
// main.js

import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';

const cache = new InMemoryCache();

现在,我们需要将缓存添加到我们的 Apollo 客户端选项中

js
//main.js

const apolloClient = new ApolloClient({
  cache,
  typeDefs,
  resolvers: {},
});

现在缓存为空。要将一些初始数据添加到缓存中,我们需要使用 writeData 方法

js
// main.js

const apolloClient = new ApolloClient({
  cache,
  typeDefs,
  resolvers: {},
});

cache.writeData({
  data: {
    todoItems: [
      {
        __typename: 'Item',
        id: 'dqdBHJGgjgjg',
        text: 'test',
        done: true,
      },
    ],
  },
});

我们刚刚将一个 todoItems 数组添加到我们的缓存数据中,并且我们定义了每个项目都具有 Item 的类型名称(在我们的本地模式中指定)。

查询本地数据

查询本地缓存与 向远程服务器发送 GraphQL 查询 非常相似。首先,我们需要创建一个查询

js
// App.vue

import gql from 'graphql-tag';

const todoItemsQuery = gql`
  {
    todoItems @client {
      id
      text
      done
    }
  }
`;

与向远程 API 发送查询的主要区别在于 @client 指令。此指令指定此查询不应针对远程 GraqhQL API 执行。相反,Apollo 客户端应从本地缓存中获取结果。

现在,我们可以在 Vue 组件中像使用普通的 Apollo 查询一样使用此查询

js
// App.vue

apollo: {
  todoItems: {
    query: todoItemsQuery
  }
},

使用变异更改本地数据

我们有两种不同的方法可以更改本地数据

  • 直接使用 writeData 方法写入,就像我们在 缓存初始化 期间所做的那样;
  • 调用 GraphQL 变异。

让我们将一些变异添加到我们的 本地 GraphQL 模式

js
// main.js

export const typeDefs = gql`
  type Item {
    id: ID!
    text: String!
    done: Boolean!
  }

  type Mutation {
    checkItem(id: ID!): Boolean
    addItem(text: String!): Item
  }
`;

checkItem 变异将把特定项目的布尔值 done 属性设置为相反的值。让我们使用 gql 创建它

js
// App.vue

const checkItemMutation = gql`
  mutation($id: ID!) {
    checkItem(id: $id) @client
  }
`;

我们定义了一个 *本地* 变异(因为我们这里有一个 @client 指令),它将接受一个唯一标识符作为参数。现在,我们需要一个 *解析器*:一个函数,它为模式中的类型或字段解析一个值。

在我们的例子中,解析器将定义当我们有一个特定变异时,我们想要对我们的本地 Apollo 缓存进行哪些更改。本地解析器与远程解析器具有相同的函数签名((parent, args, context, info) => data)。实际上,我们只需要参数(传递给变异的参数)和上下文(我们将需要它的缓存属性来读取和写入数据)。

让我们将解析器添加到我们的主文件中

js
// main.js

const resolvers = {
  Mutation: {
    checkItem: (_, { id }, { cache }) => {
      const data = cache.readQuery({ query: todoItemsQuery });
      const currentItem = data.todoItems.find(item => item.id === id);
      currentItem.done = !currentItem.done;
      cache.writeQuery({ query: todoItemsQuery, data });
      return currentItem.done;
    },
};

我们在这里做了什么?

  1. 从我们的缓存中读取 todoItemsQuery,以查看我们现在有哪些 todoItems
  2. 查找具有给定 ID 的项目;
  3. 将找到的项目的 done 属性更改为相反的值;
  4. 将我们更改后的 todoItems 写回缓存;
  5. done 属性作为变异结果返回。

现在,我们需要用新创建的 resolvers 替换 Apollo 客户端选项中的空 resolvers 对象

js
// main.js

const resolvers = {
  Mutation: {
    checkItem: (_, { id }, { cache }) => {
      const data = cache.readQuery({ query: todoItemsQuery });
      const currentItem = data.todoItems.find(item => item.id === id);
      currentItem.done = !currentItem.done;
      cache.writeQuery({ query: todoItemsQuery, data });
      return currentItem.done;
    },
};

const apolloClient = new ApolloClient({
  cache,
  typeDefs,
  resolvers,
});

在此之后,我们可以在 Vue 组件中像使用普通的 变异 一样使用该变异

js
// App.vue

methods: {
  checkItem(id) {
    this.$apollo.mutate({
      mutation: checkItemMutation,
      variables: { id }
    });
  },
}